X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gst%2Frtsp%2Fgstrtspsrc.c;h=74baf33dcf0df58ae8a1984487371f97255d5c75;hb=7462a57640fba252a42ea59edbb8afb28cf458a5;hp=188f2a1746322dcc73d4e23426d156d74b11382c;hpb=43feb82feb30192ca973949502d81318fb0ca9a7;p=platform%2Fupstream%2Fgst-plugins-good.git diff --git a/gst/rtsp/gstrtspsrc.c b/gst/rtsp/gstrtspsrc.c index 188f2a1..74baf33 100644 --- a/gst/rtsp/gstrtspsrc.c +++ b/gst/rtsp/gstrtspsrc.c @@ -53,7 +53,7 @@ * protocols can be controlled with the #GstRTSPSrc:protocols property. * * rtspsrc currently understands SDP as the format of the session description. - * For each stream listed in the SDP a new rtp_stream%d pad will be created + * For each stream listed in the SDP a new rtp_stream\%d pad will be created * with caps derived from the SDP media description. This is a caps of mime type * "application/x-rtp" that can be connected to any available RTP depayloader * element. @@ -73,8 +73,6 @@ * ]| Establish a connection to an RTSP server and send the raw RTP packets to a * fakesink. * - * - * Last reviewed on 2006-08-18 (0.10.5) */ #ifdef HAVE_CONFIG_H @@ -91,7 +89,8 @@ #include #include -#include +#include +#include #include "gst/gst-i18n-plugin.h" @@ -124,6 +123,7 @@ enum SIGNAL_ON_SDP, SIGNAL_SELECT_STREAM, SIGNAL_NEW_MANAGER, + SIGNAL_REQUEST_RTCP_KEY, LAST_SIGNAL }; @@ -164,6 +164,46 @@ gst_rtsp_src_buffer_mode_get_type (void) return buffer_mode_type; } +enum _GstRtspSrcNtpTimeSource +{ + NTP_TIME_SOURCE_NTP, + NTP_TIME_SOURCE_UNIX, + NTP_TIME_SOURCE_RUNNING_TIME, + NTP_TIME_SOURCE_CLOCK_TIME +}; + +#define DEBUG_RTSP(__self,msg) gst_rtspsrc_print_rtsp_message (__self, msg) +#define DEBUG_SDP(__self,msg) gst_rtspsrc_print_sdp_message (__self, msg) + +#define GST_TYPE_RTSP_SRC_NTP_TIME_SOURCE (gst_rtsp_src_ntp_time_source_get_type()) +static GType +gst_rtsp_src_ntp_time_source_get_type (void) +{ + static GType ntp_time_source_type = 0; + static const GEnumValue ntp_time_source_values[] = { + {NTP_TIME_SOURCE_NTP, "NTP time based on realtime clock", "ntp"}, + {NTP_TIME_SOURCE_UNIX, "UNIX time based on realtime clock", "unix"}, + {NTP_TIME_SOURCE_RUNNING_TIME, + "Running time based on pipeline clock", + "running-time"}, + {NTP_TIME_SOURCE_CLOCK_TIME, "Pipeline clock time", "clock-time"}, + {0, NULL, NULL}, + }; + + if (!ntp_time_source_type) { + ntp_time_source_type = + g_enum_register_static ("GstRTSPSrcNtpTimeSource", + ntp_time_source_values); + } + return ntp_time_source_type; +} + +#define AES_128_KEY_LEN 16 +#define AES_256_KEY_LEN 32 + +#define HMAC_32_KEY_LEN 4 +#define HMAC_80_KEY_LEN 10 + #define DEFAULT_LOCATION NULL #define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | GST_RTSP_LOWER_TRANS_TCP #define DEFAULT_DEBUG FALSE @@ -173,7 +213,6 @@ gst_rtsp_src_buffer_mode_get_type (void) #define DEFAULT_TCP_TIMEOUT 20000000 #define DEFAULT_LATENCY_MS 2000 #define DEFAULT_DROP_ON_LATENCY FALSE -#define DEFAULT_DO_RETRANSMISSION FALSE #define DEFAULT_CONNECTION_SPEED 0 #define DEFAULT_NAT_METHOD GST_RTSP_NAT_DUMMY #define DEFAULT_DO_RTCP TRUE @@ -189,8 +228,17 @@ gst_rtsp_src_buffer_mode_get_type (void) #define DEFAULT_UDP_RECONNECT TRUE #define DEFAULT_MULTICAST_IFACE NULL #define DEFAULT_NTP_SYNC FALSE -#define DEFAULT_USE_PIPELINE_CLOCK FALSE -#define DEFAULT_TLS_VALIDATION_FLAGS G_TLS_CERTIFICATE_VALIDATE_ALL +#define DEFAULT_USE_PIPELINE_CLOCK FALSE +#define DEFAULT_TLS_VALIDATION_FLAGS G_TLS_CERTIFICATE_VALIDATE_ALL +#define DEFAULT_TLS_DATABASE NULL +#define DEFAULT_TLS_INTERACTION NULL +#define DEFAULT_DO_RETRANSMISSION TRUE +#define DEFAULT_NTP_TIME_SOURCE NTP_TIME_SOURCE_NTP +#define DEFAULT_USER_AGENT "GStreamer/" PACKAGE_VERSION + +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION +#define DEFAULT_START_POSITION 0 +#endif enum { @@ -200,10 +248,13 @@ enum PROP_DEBUG, PROP_RETRY, PROP_TIMEOUT, +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + PROP_START_POSITION, + PROP_RESUME_POSITION, +#endif PROP_TCP_TIMEOUT, PROP_LATENCY, PROP_DROP_ON_LATENCY, - PROP_DO_RETRANSMISSION, PROP_CONNECTION_SPEED, PROP_NAT_METHOD, PROP_DO_RTCP, @@ -225,7 +276,11 @@ enum PROP_USE_PIPELINE_CLOCK, PROP_SDES, PROP_TLS_VALIDATION_FLAGS, - PROP_LAST + PROP_TLS_DATABASE, + PROP_TLS_INTERACTION, + PROP_DO_RETRANSMISSION, + PROP_NTP_TIME_SOURCE, + PROP_USER_AGENT }; #define GST_TYPE_RTSP_NAT_METHOD (gst_rtsp_nat_method_get_type()) @@ -295,6 +350,17 @@ static gboolean gst_rtspsrc_loop (GstRTSPSrc * src); static gboolean gst_rtspsrc_stream_push_event (GstRTSPSrc * src, GstRTSPStream * stream, GstEvent * event); static gboolean gst_rtspsrc_push_event (GstRTSPSrc * src, GstEvent * event); +static void gst_rtspsrc_connection_flush (GstRTSPSrc * src, gboolean flush); +static void +gst_rtspsrc_print_rtsp_message (GstRTSPSrc * src, const GstRTSPMessage * msg); +static void +gst_rtspsrc_print_sdp_message (GstRTSPSrc * src, const GstSDPMessage * msg); + +typedef struct +{ + guint8 pt; + GstCaps *caps; +} PtMapItem; /* commands we send to out loop to notify it of events */ #define CMD_OPEN (1 << 0) @@ -323,6 +389,57 @@ static guint gst_rtspsrc_signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE_WITH_CODE (GstRTSPSrc, gst_rtspsrc, GST_TYPE_BIN, G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_rtspsrc_uri_handler_init)); +#ifndef GST_DISABLE_GST_DEBUG +static inline const char * +cmd_to_string (guint cmd) +{ + switch (cmd) { + case CMD_OPEN: + return "OPEN"; + case CMD_PLAY: + return "PLAY"; + case CMD_PAUSE: + return "PAUSE"; + case CMD_CLOSE: + return "CLOSE"; + case CMD_WAIT: + return "WAIT"; + case CMD_RECONNECT: + return "RECONNECT"; + case CMD_LOOP: + return "LOOP"; + } + + return "unknown"; +} +#endif + +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION +static void +gst_rtspsrc_post_error_message (GstRTSPSrc * src, GstRTSPSrcError error_id, + const gchar * error_string) +{ + GstMessage *message; + GstStructure *structure; + gboolean ret = TRUE; + + GST_ERROR_OBJECT (src, "[%d] %s", error_id, error_string); + + structure = gst_structure_new ("streaming_error", + "error_id", G_TYPE_UINT, error_id, + "error_string", G_TYPE_STRING, error_string, NULL); + + message = + gst_message_new_custom (GST_MESSAGE_ERROR, GST_OBJECT (src), structure); + + ret = gst_element_post_message (GST_ELEMENT (src), message); + if (!ret) + GST_ERROR_OBJECT (src, "fail to post error message."); + + return; +} +#endif + static gboolean default_select_stream (GstRTSPSrc * src, guint id, GstCaps * caps) { @@ -374,8 +491,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", @@ -388,7 +507,18 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) "Retry TCP transport after UDP timeout microseconds (0 = disabled)", 0, G_MAXUINT64, DEFAULT_TIMEOUT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + g_object_class_install_property (gobject_class, PROP_START_POSITION, + g_param_spec_uint64 ("pending-start-position", "set start position", + "Set start position before PLAYING request.", + 0, G_MAXUINT64, DEFAULT_START_POSITION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_RESUME_POSITION, + g_param_spec_uint64 ("resume-position", "set resume position", + "Set resume position before PLAYING request after pause.", + 0, G_MAXUINT64, DEFAULT_START_POSITION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +#endif g_object_class_install_property (gobject_class, PROP_TCP_TIMEOUT, g_param_spec_uint64 ("tcp-timeout", "TCP Timeout", "Fail after timeout microseconds on TCP connections (0 = disabled)", @@ -406,12 +536,6 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) "Tells the jitterbuffer to never exceed the given latency in size", DEFAULT_DROP_ON_LATENCY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (gobject_class, PROP_DO_RETRANSMISSION, - g_param_spec_boolean ("do-retransmission", "Do retransmission", - "Send retransmission events upstream when a packet is late", - DEFAULT_DO_RETRANSMISSION, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (gobject_class, PROP_CONNECTION_SPEED, g_param_spec_uint64 ("connection-speed", "Connection Speed", "Network connection speed in kbps (0 = unknown)", @@ -570,9 +694,10 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) g_object_class_install_property (gobject_class, PROP_USE_PIPELINE_CLOCK, g_param_spec_boolean ("use-pipeline-clock", "Use pipeline clock", - "Use the pipeline running-time to set the NTP time in the RTCP SR messages", + "Use the pipeline running-time to set the NTP time in the RTCP SR messages" + "(DEPRECATED: Use ntp-time-source property)", DEFAULT_USE_PIPELINE_CLOCK, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED)); g_object_class_install_property (gobject_class, PROP_SDES, g_param_spec_boxed ("sdes", "SDES", @@ -594,6 +719,74 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** + * GstRTSPSrc::tls-database: + * + * TLS database with anchor certificate authorities used to validate + * the server certificate. + * + * Since: 1.4 + */ + g_object_class_install_property (gobject_class, PROP_TLS_DATABASE, + g_param_spec_object ("tls-database", "TLS database", + "TLS database with anchor certificate authorities used to validate the server certificate", + G_TYPE_TLS_DATABASE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc::tls-interaction: + * + * A #GTlsInteraction object to be used when the connection or certificate + * database need to interact with the user. This will be used to prompt the + * user for passwords where necessary. + * + * Since: 1.6 + */ + g_object_class_install_property (gobject_class, PROP_TLS_INTERACTION, + g_param_spec_object ("tls-interaction", "TLS interaction", + "A GTlsInteraction object to promt the user for password or certificate", + G_TYPE_TLS_INTERACTION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc::do-retransmission: + * + * Attempt to ask the server to retransmit lost packets according to RFC4588. + * + * Note: currently only works with SSRC-multiplexed retransmission streams + * + * Since: 1.6 + */ + g_object_class_install_property (gobject_class, PROP_DO_RETRANSMISSION, + g_param_spec_boolean ("do-retransmission", "Retransmission", + "Ask the server to retransmit lost packets", + DEFAULT_DO_RETRANSMISSION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc::ntp-time-source: + * + * allows to select the time source that should be used + * for the NTP time in RTCP packets + * + * Since: 1.6 + */ + g_object_class_install_property (gobject_class, PROP_NTP_TIME_SOURCE, + g_param_spec_enum ("ntp-time-source", "NTP Time Source", + "NTP time source for RTCP packets", + GST_TYPE_RTSP_SRC_NTP_TIME_SOURCE, DEFAULT_NTP_TIME_SOURCE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPSrc::user-agent: + * + * The string to set in the User-Agent header. + * + * Since: 1.6 + */ + g_object_class_install_property (gobject_class, PROP_USER_AGENT, + g_param_spec_string ("user-agent", "User Agent", + "The User-Agent string to send to the server", + DEFAULT_USER_AGENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** * GstRTSPSrc::handle-request: * @rtspsrc: a #GstRTSPSrc * @request: a #GstRTSPMessage @@ -670,6 +863,21 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_CLEANUP, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GST_TYPE_ELEMENT); + /** + * GstRTSPSrc::request-rtcp-key: + * @rtspsrc: a #GstRTSPSrc + * @num: the stream number + * + * Signal emited to get the crypto parameters relevant to the RTCP + * stream. User should provide the key and the RTCP encryption ciphers + * and authentication, and return them wrapped in a GstCaps. + * + * Since: 1.4 + */ + gst_rtspsrc_signals[SIGNAL_REQUEST_RTCP_KEY] = + g_signal_new ("request-rtcp-key", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT); + gstelement_class->send_event = gst_rtspsrc_send_event; gstelement_class->provide_clock = gst_rtspsrc_provide_clock; gstelement_class->change_state = gst_rtspsrc_change_state; @@ -697,10 +905,17 @@ gst_rtspsrc_init (GstRTSPSrc * src) src->debug = DEFAULT_DEBUG; src->retry = DEFAULT_RETRY; src->udp_timeout = DEFAULT_TIMEOUT; +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + src->start_position = DEFAULT_START_POSITION; + src->is_audio_codec_supported = FALSE; + src->is_video_codec_supported = FALSE; + src->audio_codec = NULL; + src->video_codec = NULL; + src->video_frame_size = NULL; +#endif gst_rtspsrc_set_tcp_timeout (src, DEFAULT_TCP_TIMEOUT); src->latency = DEFAULT_LATENCY_MS; src->drop_on_latency = DEFAULT_DROP_ON_LATENCY; - src->do_retransmission = DEFAULT_DO_RETRANSMISSION; src->connection_speed = DEFAULT_CONNECTION_SPEED; src->nat_method = DEFAULT_NAT_METHOD; src->do_rtcp = DEFAULT_DO_RTCP; @@ -721,7 +936,16 @@ gst_rtspsrc_init (GstRTSPSrc * src) src->use_pipeline_clock = DEFAULT_USE_PIPELINE_CLOCK; src->sdes = NULL; src->tls_validation_flags = DEFAULT_TLS_VALIDATION_FLAGS; + src->tls_database = DEFAULT_TLS_DATABASE; + src->tls_interaction = DEFAULT_TLS_INTERACTION; + src->do_retransmission = DEFAULT_DO_RETRANSMISSION; + src->ntp_time_source = DEFAULT_NTP_TIME_SOURCE; + src->user_agent = g_strdup (DEFAULT_USER_AGENT); +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + g_mutex_init (&(src)->pause_lock); + g_cond_init (&(src)->open_end); +#endif /* get a list of all extensions */ src->extensions = gst_rtsp_ext_list_get (); @@ -748,6 +972,22 @@ gst_rtspsrc_finalize (GObject * object) rtspsrc = GST_RTSPSRC (object); +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + rtspsrc->is_audio_codec_supported = FALSE; + rtspsrc->is_video_codec_supported = FALSE; + if (rtspsrc->audio_codec) { + g_free (rtspsrc->audio_codec); + rtspsrc->audio_codec = NULL; + } + if (rtspsrc->video_codec) { + g_free (rtspsrc->video_codec); + rtspsrc->video_codec = NULL; + } + if (rtspsrc->video_frame_size) { + g_free (rtspsrc->video_frame_size); + rtspsrc->video_frame_size = NULL; + } +#endif gst_rtsp_ext_list_free (rtspsrc->extensions); g_free (rtspsrc->conninfo.location); gst_rtsp_url_free (rtspsrc->conninfo.url); @@ -755,6 +995,12 @@ gst_rtspsrc_finalize (GObject * object) g_free (rtspsrc->user_id); g_free (rtspsrc->user_pw); g_free (rtspsrc->multi_iface); + g_free (rtspsrc->user_agent); + +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + g_mutex_clear (&(rtspsrc)->pause_lock); + g_cond_clear (&(rtspsrc)->open_end); +#endif if (rtspsrc->sdp) { gst_sdp_message_free (rtspsrc->sdp); @@ -766,6 +1012,12 @@ gst_rtspsrc_finalize (GObject * object) if (rtspsrc->sdes) gst_structure_free (rtspsrc->sdes); + if (rtspsrc->tls_database) + g_object_unref (rtspsrc->tls_database); + + if (rtspsrc->tls_interaction) + g_object_unref (rtspsrc->tls_interaction); + /* free locks */ g_rec_mutex_clear (&rtspsrc->stream_rec_lock); g_rec_mutex_clear (&rtspsrc->state_rec_lock); @@ -882,6 +1134,16 @@ gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value, case PROP_TIMEOUT: rtspsrc->udp_timeout = g_value_get_uint64 (value); break; +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + case PROP_START_POSITION: + rtspsrc->start_position = g_value_get_uint64 (value); + break; + case PROP_RESUME_POSITION: + rtspsrc->last_pos = g_value_get_uint64 (value); + GST_DEBUG_OBJECT (rtspsrc, "src->last_pos value set to %" GST_TIME_FORMAT, + GST_TIME_ARGS (rtspsrc->last_pos)); + break; +#endif case PROP_TCP_TIMEOUT: gst_rtspsrc_set_tcp_timeout (rtspsrc, g_value_get_uint64 (value)); break; @@ -891,9 +1153,6 @@ gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value, case PROP_DROP_ON_LATENCY: rtspsrc->drop_on_latency = g_value_get_boolean (value); break; - case PROP_DO_RETRANSMISSION: - rtspsrc->do_retransmission = g_value_get_boolean (value); - break; case PROP_CONNECTION_SPEED: rtspsrc->connection_speed = g_value_get_uint64 (value); break; @@ -981,6 +1240,24 @@ gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value, case PROP_TLS_VALIDATION_FLAGS: rtspsrc->tls_validation_flags = g_value_get_flags (value); break; + case PROP_TLS_DATABASE: + g_clear_object (&rtspsrc->tls_database); + rtspsrc->tls_database = g_value_dup_object (value); + break; + case PROP_TLS_INTERACTION: + g_clear_object (&rtspsrc->tls_interaction); + rtspsrc->tls_interaction = g_value_dup_object (value); + break; + case PROP_DO_RETRANSMISSION: + rtspsrc->do_retransmission = g_value_get_boolean (value); + break; + case PROP_NTP_TIME_SOURCE: + rtspsrc->ntp_time_source = g_value_get_enum (value); + break; + case PROP_USER_AGENT: + g_free (rtspsrc->user_agent); + rtspsrc->user_agent = g_value_dup_string (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1011,6 +1288,14 @@ gst_rtspsrc_get_property (GObject * object, guint prop_id, GValue * value, case PROP_TIMEOUT: g_value_set_uint64 (value, rtspsrc->udp_timeout); break; +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + case PROP_START_POSITION: + g_value_set_uint64 (value, rtspsrc->start_position); + break; + case PROP_RESUME_POSITION: + g_value_set_uint64 (value, rtspsrc->last_pos); + break; +#endif case PROP_TCP_TIMEOUT: { guint64 timeout; @@ -1026,9 +1311,6 @@ gst_rtspsrc_get_property (GObject * object, guint prop_id, GValue * value, case PROP_DROP_ON_LATENCY: g_value_set_boolean (value, rtspsrc->drop_on_latency); break; - case PROP_DO_RETRANSMISSION: - g_value_set_boolean (value, rtspsrc->do_retransmission); - break; case PROP_CONNECTION_SPEED: g_value_set_uint64 (value, rtspsrc->connection_speed); break; @@ -1112,6 +1394,21 @@ gst_rtspsrc_get_property (GObject * object, guint prop_id, GValue * value, case PROP_TLS_VALIDATION_FLAGS: g_value_set_flags (value, rtspsrc->tls_validation_flags); break; + case PROP_TLS_DATABASE: + g_value_set_object (value, rtspsrc->tls_database); + break; + case PROP_TLS_INTERACTION: + g_value_set_object (value, rtspsrc->tls_interaction); + break; + case PROP_DO_RETRANSMISSION: + g_value_set_boolean (value, rtspsrc->do_retransmission); + break; + case PROP_NTP_TIME_SOURCE: + g_value_set_enum (value, rtspsrc->ntp_time_source); + break; + case PROP_USER_AGENT: + g_value_set_string (value, rtspsrc->user_agent); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1137,15 +1434,6 @@ find_stream_by_channel (GstRTSPStream * stream, gint * channel) } static gint -find_stream_by_pt (GstRTSPStream * stream, gint * pt) -{ - if (stream->pt == *pt) - return 0; - - return -1; -} - -static gint find_stream_by_udpsrc (GstRTSPStream * stream, gconstpointer a) { GstElement *src = (GstElement *) a; @@ -1161,16 +1449,20 @@ find_stream_by_udpsrc (GstRTSPStream * stream, gconstpointer a) static gint find_stream_by_setup (GstRTSPStream * stream, gconstpointer a) { - /* check qualified setup_url */ - if (!strcmp (stream->conninfo.location, (gchar *) a)) - return 0; - /* check original control_url */ - if (!strcmp (stream->control_url, (gchar *) a)) - return 0; + if (stream->conninfo.location) { + /* check qualified setup_url */ + if (!strcmp (stream->conninfo.location, (gchar *) a)) + return 0; + } + if (stream->control_url) { + /* check original control_url */ + if (!strcmp (stream->control_url, (gchar *) a)) + return 0; - /* check if qualified setup_url ends with string */ - if (g_str_has_suffix (stream->control_url, (gchar *) a)) - return 0; + /* check if qualified setup_url ends with string */ + if (g_str_has_suffix (stream->control_url, (gchar *) a)) + return 0; + } return -1; } @@ -1288,6 +1580,143 @@ gst_rtspsrc_collect_connections (GstRTSPSrc * src, const GstSDPMessage * sdp, } } +/* m= RTP/AVP + */ +static void +gst_rtspsrc_collect_payloads (GstRTSPSrc * src, const GstSDPMessage * sdp, + const GstSDPMedia * media, GstRTSPStream * stream) +{ + guint i, len; + const gchar *proto; + GstCaps *global_caps; + + /* get proto */ + proto = gst_sdp_media_get_proto (media); + if (proto == NULL) + goto no_proto; + + if (g_str_equal (proto, "RTP/AVP")) + stream->profile = GST_RTSP_PROFILE_AVP; + else if (g_str_equal (proto, "RTP/SAVP")) + stream->profile = GST_RTSP_PROFILE_SAVP; + else if (g_str_equal (proto, "RTP/AVPF")) + stream->profile = GST_RTSP_PROFILE_AVPF; + else if (g_str_equal (proto, "RTP/SAVPF")) + stream->profile = GST_RTSP_PROFILE_SAVPF; + else + goto unknown_proto; + + /* Parse global SDP attributes once */ + global_caps = gst_caps_new_empty_simple ("application/x-unknown"); + GST_DEBUG ("mapping sdp session level attributes to caps"); + gst_rtspsrc_sdp_attributes_to_caps (sdp->attributes, global_caps); + GST_DEBUG ("mapping sdp media level attributes to caps"); + gst_rtspsrc_sdp_attributes_to_caps (media->attributes, global_caps); + + len = gst_sdp_media_formats_len (media); + for (i = 0; i < len; i++) { + gint pt; + GstCaps *caps, *outcaps; + GstStructure *s; + const gchar *enc; + PtMapItem item; +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + const gchar *encoder, *mediatype; +#endif + pt = atoi (gst_sdp_media_get_format (media, i)); + + GST_DEBUG_OBJECT (src, " looking at %d pt: %d", i, pt); + + /* convert caps */ + caps = gst_rtspsrc_media_to_caps (pt, media); + if (caps == NULL) { + GST_WARNING_OBJECT (src, " skipping pt %d without caps", pt); + continue; + } + + /* do some tweaks */ + s = gst_caps_get_structure (caps, 0); + if ((enc = gst_structure_get_string (s, "encoding-name"))) { + stream->is_real = (strstr (enc, "-REAL") != NULL); + if (strcmp (enc, "X-ASF-PF") == 0) + stream->container = TRUE; + } +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + if ((mediatype = gst_structure_get_string (s, "media"))) { + GST_DEBUG_OBJECT (src, " mediatype : %s", mediatype); + if (!strcmp (mediatype, "video")) { + if ((encoder = gst_structure_get_string (s, "encoding-name"))) { + GST_DEBUG_OBJECT (src, " encoder : %s", encoder); + if ((!strcmp (encoder, "H261")) || + (!strcmp (encoder, "H263")) || + (!strcmp (encoder, "H263-1998")) + || (!strcmp (encoder, "H263-2000")) || (!strcmp (encoder, "H264")) + || (!strcmp (encoder, "MP4V-ES"))) { + src->is_video_codec_supported = TRUE; + GST_DEBUG_OBJECT (src, "Supported Video Codec %s", encoder); + } else { + GST_DEBUG_OBJECT (src, "Unsupported Video Codec %s", encoder); + } + } + + src->video_codec = g_strdup (encoder); + src->video_frame_size = + g_strdup (gst_structure_get_string (s, "a-framesize")); + GST_DEBUG_OBJECT (src, "video_codec %s , video_frame_size %s ", + src->video_codec, src->video_frame_size); + } else if (!strcmp (mediatype, "audio")) { + if ((encoder = gst_structure_get_string (s, "encoding-name"))) { + GST_DEBUG_OBJECT (src, " encoder : %s", encoder); + if ((!strcmp (encoder, "MP4A-LATM")) || + (!strcmp (encoder, "AMR")) || (!strcmp (encoder, "AMR-WB")) + || (!strcmp (encoder, "AMR-NB")) + || (!strcmp (encoder, "mpeg4-generic")) + || (!strcmp (encoder, "MPEG4-GENERIC")) + || (!strcmp (encoder, "QCELP")) || ((strstr (encoder, "G726")) + || (strstr (encoder, "PCMU")))) { + src->is_audio_codec_supported = TRUE; + GST_DEBUG_OBJECT (src, "Supported Audio Codec %s", encoder); + } else { + GST_DEBUG_OBJECT (src, "Unsupported Audio Codec %s", encoder); + } + } + + src->audio_codec = g_strdup (encoder); + GST_DEBUG_OBJECT (src, "audio_codec %s ", src->audio_codec); + } + } +#endif + + /* Merge in global caps */ + /* Intersect will merge in missing fields to the current caps */ + outcaps = gst_caps_intersect (caps, global_caps); + gst_caps_unref (caps); + + /* the first pt will be the default */ + if (stream->ptmap->len == 0) + stream->default_pt = pt; + + item.pt = pt; + item.caps = outcaps; + + g_array_append_val (stream->ptmap, item); + } + + gst_caps_unref (global_caps); + return; + +no_proto: + { + GST_ERROR_OBJECT (src, "can't find proto in media"); + return; + } +unknown_proto: + { + GST_ERROR_OBJECT (src, "unknown proto in media %s", proto); + return; + } +} + static const gchar * get_aggregate_control (GstRTSPSrc * src) { @@ -1305,12 +1734,18 @@ get_aggregate_control (GstRTSPSrc * src) return base; } +static void +clear_ptmap_item (PtMapItem * item) +{ + if (item->caps) + gst_caps_unref (item->caps); +} + static GstRTSPStream * gst_rtspsrc_create_stream (GstRTSPSrc * src, GstSDPMessage * sdp, gint idx) { GstRTSPStream *stream; const gchar *control_url; - const gchar *payload; const GstSDPMedia *media; /* get media, should not return NULL */ @@ -1324,12 +1759,17 @@ gst_rtspsrc_create_stream (GstRTSPSrc * src, GstSDPMessage * sdp, gint idx) * the element. */ stream->last_ret = GST_FLOW_NOT_LINKED; stream->added = FALSE; - stream->disabled = FALSE; - stream->id = src->numstreams++; + stream->setup = FALSE; + stream->skipped = FALSE; + stream->id = idx; stream->eos = FALSE; stream->discont = TRUE; stream->seqbase = -1; stream->timebase = -1; + stream->send_ssrc = g_random_int (); + stream->profile = GST_RTSP_PROFILE_AVP; + stream->ptmap = g_array_new (FALSE, FALSE, sizeof (PtMapItem)); + g_array_set_clear_func (stream->ptmap, (GDestroyNotify) clear_ptmap_item); /* collect bandwidth information for this steam. FIXME, configure in the RTP * session manager to scale RTCP. */ @@ -1338,33 +1778,9 @@ gst_rtspsrc_create_stream (GstRTSPSrc * src, GstSDPMessage * sdp, gint idx) /* collect connection info */ gst_rtspsrc_collect_connections (src, sdp, media, stream); - /* we must have a payload. No payload means we cannot create caps */ - /* FIXME, handle multiple formats. The problem here is that we just want to - * take the first available format that we can handle but in order to do that - * we need to scan for depayloader plugins. Scanning for payloader plugins is - * also suboptimal because the user maybe just wants to save the raw stream - * and then we don't care. */ - if ((payload = gst_sdp_media_get_format (media, 0))) { - stream->pt = atoi (payload); - /* convert caps */ - stream->caps = gst_rtspsrc_media_to_caps (stream->pt, media); - - GST_DEBUG ("mapping sdp session level attributes to caps"); - gst_rtspsrc_sdp_attributes_to_caps (sdp->attributes, stream->caps); - GST_DEBUG ("mapping sdp media level attributes to caps"); - gst_rtspsrc_sdp_attributes_to_caps (media->attributes, stream->caps); - - if (stream->pt >= 96) { - /* If we have a dynamic payload type, see if we have a stream with the - * same payload number. If there is one, they are part of the same - * container and we only need to add one pad. */ - if (find_stream (src, &stream->pt, (gpointer) find_stream_by_pt)) { - stream->container = TRUE; - GST_DEBUG ("found another stream with pt %d, marking as container", - stream->pt); - } - } - } + /* make the payload type map */ + gst_rtspsrc_collect_payloads (src, sdp, media, stream); + /* collect port number */ stream->port = gst_sdp_media_get_port (media); @@ -1376,10 +1792,8 @@ gst_rtspsrc_create_stream (GstRTSPSrc * src, GstSDPMessage * sdp, gint idx) control_url = gst_sdp_message_get_attribute_val_n (sdp, "control", 0); GST_DEBUG_OBJECT (src, "stream %d, (%p)", stream->id, stream); - GST_DEBUG_OBJECT (src, " pt: %d", stream->pt); GST_DEBUG_OBJECT (src, " port: %d", stream->port); GST_DEBUG_OBJECT (src, " container: %d", stream->container); - GST_DEBUG_OBJECT (src, " caps: %" GST_PTR_FORMAT, stream->caps); GST_DEBUG_OBJECT (src, " control: %s", GST_STR_NULL (control_url)); if (control_url != NULL) { @@ -1427,8 +1841,7 @@ gst_rtspsrc_stream_free (GstRTSPSrc * src, GstRTSPStream * stream) GST_DEBUG_OBJECT (src, "free stream %p", stream); - if (stream->caps) - gst_caps_unref (stream->caps); + g_array_free (stream->ptmap, TRUE); g_free (stream->destination); g_free (stream->control_url); @@ -1439,41 +1852,38 @@ gst_rtspsrc_stream_free (GstRTSPSrc * src, GstRTSPStream * stream) gst_element_set_state (stream->udpsrc[i], GST_STATE_NULL); gst_bin_remove (GST_BIN_CAST (src), stream->udpsrc[i]); gst_object_unref (stream->udpsrc[i]); - stream->udpsrc[i] = NULL; } - if (stream->channelpad[i]) { + if (stream->channelpad[i]) gst_object_unref (stream->channelpad[i]); - stream->channelpad[i] = NULL; - } + if (stream->udpsink[i]) { gst_element_set_state (stream->udpsink[i], GST_STATE_NULL); gst_bin_remove (GST_BIN_CAST (src), stream->udpsink[i]); gst_object_unref (stream->udpsink[i]); - stream->udpsink[i] = NULL; } } 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); - stream->fakesrc = NULL; } if (stream->srcpad) { gst_pad_set_active (stream->srcpad, FALSE); - if (stream->added) { + if (stream->added) gst_element_remove_pad (GST_ELEMENT_CAST (src), stream->srcpad); - stream->added = FALSE; - } - stream->srcpad = NULL; } - if (stream->rtcppad) { + if (stream->srtpenc) + gst_object_unref (stream->srtpenc); + if (stream->srtpdec) + gst_object_unref (stream->srtpdec); + if (stream->srtcpparams) + gst_caps_unref (stream->srtcpparams); + if (stream->rtcppad) gst_object_unref (stream->rtcppad); - stream->rtcppad = NULL; - } - if (stream->session) { + if (stream->session) g_object_unref (stream->session); - stream->session = NULL; - } + if (stream->rtx_pt_map) + gst_structure_free (stream->rtx_pt_map); g_free (stream); } @@ -1500,7 +1910,6 @@ gst_rtspsrc_cleanup (GstRTSPSrc * src) gst_bin_remove (GST_BIN_CAST (src), src->manager); src->manager = NULL; } - src->numstreams = 0; if (src->props) gst_structure_free (src->props); src->props = NULL; @@ -1520,10 +1929,9 @@ gst_rtspsrc_cleanup (GstRTSPSrc * src) gst_sdp_message_free (src->sdp); src->sdp = NULL; } - if (src->start_segment) { - gst_event_unref (src->start_segment); - src->start_segment = NULL; - } + + src->need_segment = FALSE; + if (src->provided_clock) { gst_object_unref (src->provided_clock); src->provided_clock = NULL; @@ -1610,6 +2018,162 @@ gst_rtspsrc_parse_rtpmap (const gchar * rtpmap, gint * payload, gchar ** name, return TRUE; } +static gboolean +parse_keymgmt (const gchar * keymgmt, GstCaps * caps) +{ + gboolean res = FALSE; + gsize size; + guchar *data; + GstMIKEYMessage *msg; + const GstMIKEYPayload *payload; + const gchar *srtp_cipher; + const gchar *srtp_auth; + + { + gchar *orig_value; + gchar *p, *kmpid; + + p = orig_value = g_strdup (keymgmt); + + SKIP_SPACES (p); + if (*p == '\0') { + g_free (orig_value); + return FALSE; + } + + PARSE_STRING (p, " ", kmpid); + if (kmpid == NULL || !g_str_equal (kmpid, "mikey")) { + g_free (orig_value); + return FALSE; + } + data = g_base64_decode (p, &size); + + g_free (orig_value); /* Don't need this any more */ + } + + if (data == NULL) + return FALSE; + + msg = gst_mikey_message_new_from_data (data, size, NULL, NULL); + g_free (data); + if (msg == NULL) + return FALSE; + + srtp_cipher = "aes-128-icm"; + srtp_auth = "hmac-sha1-80"; + + /* check the Security policy if any */ + if ((payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_SP, 0))) { + GstMIKEYPayloadSP *p = (GstMIKEYPayloadSP *) payload; + guint len, i; + + if (p->proto != GST_MIKEY_SEC_PROTO_SRTP) + goto done; + + len = gst_mikey_payload_sp_get_n_params (payload); + for (i = 0; i < len; i++) { + const GstMIKEYPayloadSPParam *param = + gst_mikey_payload_sp_get_param (payload, i); + + switch (param->type) { + case GST_MIKEY_SP_SRTP_ENC_ALG: + switch (param->val[0]) { + case 0: + srtp_cipher = "null"; + break; + case 2: + case 1: + srtp_cipher = "aes-128-icm"; + break; + default: + break; + } + break; + case GST_MIKEY_SP_SRTP_ENC_KEY_LEN: + switch (param->val[0]) { + case AES_128_KEY_LEN: + srtp_cipher = "aes-128-icm"; + break; + case AES_256_KEY_LEN: + srtp_cipher = "aes-256-icm"; + break; + default: + break; + } + break; + case GST_MIKEY_SP_SRTP_AUTH_ALG: + switch (param->val[0]) { + case 0: + srtp_auth = "null"; + break; + case 2: + case 1: + srtp_auth = "hmac-sha1-80"; + break; + default: + break; + } + break; + case GST_MIKEY_SP_SRTP_AUTH_KEY_LEN: + switch (param->val[0]) { + case HMAC_32_KEY_LEN: + srtp_auth = "hmac-sha1-32"; + break; + case HMAC_80_KEY_LEN: + srtp_auth = "hmac-sha1-80"; + break; + default: + break; + } + break; + case GST_MIKEY_SP_SRTP_SRTP_ENC: + break; + case GST_MIKEY_SP_SRTP_SRTCP_ENC: + break; + default: + break; + } + } + } + + if (!(payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_KEMAC, 0))) + goto done; + else { + GstMIKEYPayloadKEMAC *p = (GstMIKEYPayloadKEMAC *) payload; + const GstMIKEYPayload *sub; + GstMIKEYPayloadKeyData *pkd; + GstBuffer *buf; + + if (p->enc_alg != GST_MIKEY_ENC_NULL || p->mac_alg != GST_MIKEY_MAC_NULL) + goto done; + + if (!(sub = gst_mikey_payload_kemac_get_sub (payload, 0))) + goto done; + + if (sub->type != GST_MIKEY_PT_KEY_DATA) + goto done; + + pkd = (GstMIKEYPayloadKeyData *) sub; + buf = + gst_buffer_new_wrapped (g_memdup (pkd->key_data, pkd->key_len), + pkd->key_len); + gst_caps_set_simple (caps, "srtp-key", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + } + + gst_caps_set_simple (caps, + "srtp-cipher", G_TYPE_STRING, srtp_cipher, + "srtp-auth", G_TYPE_STRING, srtp_auth, + "srtcp-cipher", G_TYPE_STRING, srtp_cipher, + "srtcp-auth", G_TYPE_STRING, srtp_auth, NULL); + + res = TRUE; +done: + gst_mikey_message_unref (msg); + + return res; +} + /* * Mapping SDP attributes to caps * @@ -1641,6 +2205,12 @@ gst_rtspsrc_sdp_attributes_to_caps (GArray * attributes, GstCaps * caps) continue; if (!strcmp (key, "range")) continue; + if (!strcmp (key, "framesize")) + continue; + if (g_str_equal (key, "key-mgmt")) { + parse_keymgmt (attr->value, caps); + continue; + } /* string must be valid UTF8 */ if (!g_utf8_validate (attr->value, -1, NULL)) @@ -1658,11 +2228,33 @@ gst_rtspsrc_sdp_attributes_to_caps (GArray * attributes, GstCaps * caps) } } +static const gchar * +rtsp_get_attribute_for_pt (const GstSDPMedia * media, const gchar * name, + gint pt) +{ + guint i; + + for (i = 0;; i++) { + const gchar *attr; + gint val; + + if ((attr = gst_sdp_media_get_attribute_val_n (media, name, i)) == NULL) + break; + + if (sscanf (attr, "%d ", &val) != 1) + continue; + + if (val == pt) + return attr; + } + return NULL; +} + /* * Mapping of caps to and from SDP fields: * - * m= RTP/AVP * a=rtpmap: /[/] + * a=framesize: - * a=fmtp: [=];... */ static GstCaps * @@ -1671,6 +2263,7 @@ gst_rtspsrc_media_to_caps (gint pt, const GstSDPMedia * media) GstCaps *caps; const gchar *rtpmap; const gchar *fmtp; + const gchar *framesize; gchar *name = NULL; gint rate = -1; gchar *params = NULL; @@ -1680,29 +2273,19 @@ gst_rtspsrc_media_to_caps (gint pt, const GstSDPMedia * media) gboolean ret; /* get and parse rtpmap */ - if ((rtpmap = gst_sdp_media_get_attribute_val (media, "rtpmap"))) { + rtpmap = rtsp_get_attribute_for_pt (media, "rtpmap", pt); + + if (rtpmap) { ret = gst_rtspsrc_parse_rtpmap (rtpmap, &payload, &name, &rate, ¶ms); - if (ret) { - if (payload != pt) { - /* we ignore the rtpmap if the payload type is different. */ - g_warning ("rtpmap of wrong payload type, ignoring"); - name = NULL; - rate = -1; - params = NULL; - } - } else { - /* if we failed to parse the rtpmap for a dynamic payload type, we have an - * error */ - if (pt >= 96) - goto no_rtpmap; - /* else we can ignore */ + if (!ret) { g_warning ("error parsing rtpmap, ignoring"); + rtpmap = NULL; } - } else { - /* dynamic payloads need rtpmap or we fail */ - if (pt >= 96) - goto no_rtpmap; } + /* dynamic payloads need rtpmap or we fail */ + if (rtpmap == NULL && pt >= 96) + goto no_rtpmap; + /* check if we have a rate, if not, we need to look up the rate from the * default rates based on the payload types. */ if (rate == -1) { @@ -1750,7 +2333,7 @@ gst_rtspsrc_media_to_caps (gint pt, const GstSDPMedia * media) } /* parse optional fmtp: field */ - if ((fmtp = gst_sdp_media_get_attribute_val (media, "fmtp"))) { + if ((fmtp = rtsp_get_attribute_for_pt (media, "fmtp", pt))) { gchar *p; gint payload = 0; @@ -1767,6 +2350,11 @@ gst_rtspsrc_media_to_caps (gint pt, const GstSDPMedia * media) for (i = 0; pairs[i]; i++) { gchar *valpos; const gchar *val, *key; + gint j; + const gchar *reserved_keys[] = + { "media", "payload", "clock-rate", "encoding-name", + "encoding-params" + }; /* the key may not have a '=', the value can have other '='s */ valpos = strstr (pairs[i], "="); @@ -1785,6 +2373,19 @@ gst_rtspsrc_media_to_caps (gint pt, const GstSDPMedia * media) } /* strip the key of spaces, convert key to lowercase but not the value. */ key = g_strstrip (pairs[i]); + + /* skip keys from the fmtp, which we already use ourselves for the + * caps. Some software is adding random things like clock-rate into + * the fmtp, and we would otherwise here set a string-typed clock-rate + * in the caps... and thus fail to create valid RTP caps + */ + for (j = 0; j < G_N_ELEMENTS (reserved_keys); j++) { + if (g_ascii_strcasecmp (reserved_keys[j], key) == 0) { + key = ""; + break; + } + } + if (strlen (key) > 1) { tmp = g_ascii_strdown (key, -1); gst_structure_set (s, tmp, G_TYPE_STRING, val, NULL); @@ -1794,6 +2395,19 @@ gst_rtspsrc_media_to_caps (gint pt, const GstSDPMedia * media) g_strfreev (pairs); } } + + /* parse framesize: field */ + if ((framesize = gst_sdp_media_get_attribute_val (media, "framesize"))) { + gchar *p; + + /* p is now of the format - */ + p = (gchar *) framesize; + + PARSE_INT (p, " ", payload); + if (payload != -1 && payload == pt) { + gst_structure_set (s, "a-framesize", G_TYPE_STRING, p, NULL); + } + } return caps; /* ERRORS */ @@ -1851,7 +2465,7 @@ again: g_object_set (G_OBJECT (udpsrc0), "buffer-size", src->udp_buffer_size, NULL); - ret = gst_element_set_state (udpsrc0, GST_STATE_PAUSED); + ret = gst_element_set_state (udpsrc0, GST_STATE_READY); if (ret == GST_STATE_CHANGE_FAILURE) { if (tmp_rtp != 0) { GST_DEBUG_OBJECT (src, "Unable to make udpsrc from RTP port %d", tmp_rtp); @@ -1905,7 +2519,7 @@ again: g_object_set (G_OBJECT (udpsrc1), "port", tmp_rtcp, "reuse", FALSE, NULL); GST_DEBUG_OBJECT (src, "starting RTCP on port %d", tmp_rtcp); - ret = gst_element_set_state (udpsrc1, GST_STATE_PAUSED); + ret = gst_element_set_state (udpsrc1, GST_STATE_READY); /* tmp_rtcp port is busy already : retry to make rtp/rtcp pair */ if (ret == GST_STATE_CHANGE_FAILURE) { GST_DEBUG_OBJECT (src, "Unable to make udpsrc from RTCP port %d", tmp_rtcp); @@ -1992,6 +2606,9 @@ gst_rtspsrc_set_state (GstRTSPSrc * src, GstState state) { GList *walk; + GST_WARNING_OBJECT (src, "Setting [%s] element state to: %s \n", + GST_ELEMENT_NAME (GST_ELEMENT_CAST (src)), + gst_element_state_get_name (state)); if (src->manager) gst_element_set_state (GST_ELEMENT_CAST (src->manager), state); @@ -2079,22 +2696,16 @@ gst_rtspsrc_get_position (GstRTSPSrc * src) GST_DEBUG_OBJECT (src, "retaining position %" GST_TIME_FORMAT, GST_TIME_ARGS (pos)); src->last_pos = pos; - return; + goto out; } } } src->last_pos = 0; -} -static gboolean -gst_rtspsrc_do_seek (GstRTSPSrc * src, GstSegment * segment) -{ - src->state = GST_RTSP_STATE_SEEKING; - /* PLAY will add the range header now. */ - src->need_range = TRUE; +out: - return TRUE; + gst_query_unref (query); } static gboolean @@ -2156,6 +2767,9 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event) GST_DEBUG_OBJECT (src, "stopped streaming"); + /* stop flushing the rtsp connection so we can send PAUSE/PLAY below */ + gst_rtspsrc_connection_flush (src, FALSE); + /* copy segment, we need this because we still need the old * segment when we close the current segment. */ memcpy (&seeksegment, &src->segment, sizeof (GstSegment)); @@ -2183,7 +2797,10 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event) } src->skip = skip; - gst_rtspsrc_do_seek (src, &seeksegment); + src->state = GST_RTSP_STATE_SEEKING; + + /* PLAY will add the range header now. */ + src->need_range = TRUE; /* and continue playing */ if (playing) @@ -2391,9 +3008,8 @@ gst_rtspsrc_handle_src_query (GstPad * pad, GstObject * parent, seekable = seekable && src->seekable && src->segment.duration && GST_CLOCK_TIME_IS_VALID (src->segment.duration); - /* FIXME ?? should we have 0 and segment.duration here; see demuxers */ - gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, - src->segment.start, src->segment.stop); + gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, 0, + src->segment.duration); res = TRUE; } break; @@ -2500,6 +3116,17 @@ was_ok: } } +static gboolean +copy_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data) +{ + GstPad *gpad = GST_PAD_CAST (user_data); + + GST_DEBUG_OBJECT (gpad, "store sticky event %" GST_PTR_FORMAT, *event); + gst_pad_store_sticky_event (gpad, *event); + + return TRUE; +} + /* this callback is called when the session manager generated a new src pad with * payloaded RTP packets. We simply ghost the pad here. */ static void @@ -2537,12 +3164,12 @@ new_manager_pad (GstElement * manager, GstPad * pad, GstRTSPSrc * src) for (ostreams = src->streams; ostreams; ostreams = g_list_next (ostreams)) { GstRTSPStream *ostream = (GstRTSPStream *) ostreams->data; - GST_DEBUG_OBJECT (src, "stream %p, container %d, disabled %d, added %d", - ostream, ostream->container, ostream->disabled, ostream->added); + GST_DEBUG_OBJECT (src, "stream %p, container %d, added %d, setup %d", + ostream, ostream->container, ostream->added, ostream->setup); - /* a container stream only needs one pad added. Also disabled streams don't - * count */ - if (!ostream->container && !ostream->disabled && !ostream->added) { + /* if we find a stream for which we did a setup that is not added, we + * need to wait some more */ + if (ostream->setup && !ostream->added) { all_added = FALSE; break; } @@ -2558,6 +3185,7 @@ new_manager_pad (GstElement * manager, GstPad * pad, GstRTSPSrc * 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); if (all_added) { @@ -2580,6 +3208,20 @@ unknown_stream: } static GstCaps * +stream_get_caps_for_pt (GstRTSPStream * stream, guint pt) +{ + guint i, len; + + len = stream->ptmap->len; + for (i = 0; i < len; i++) { + PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); + if (item->pt == pt) + return item->caps; + } + return NULL; +} + +static GstCaps * request_pt_map (GstElement * manager, guint session, guint pt, GstRTSPSrc * src) { GstRTSPStream *stream; @@ -2592,8 +3234,7 @@ request_pt_map (GstElement * manager, guint session, guint pt, GstRTSPSrc * src) if (!stream) goto unknown_stream; - caps = stream->caps; - if (caps) + if ((caps = stream_get_caps_for_pt (stream, pt))) gst_caps_ref (caps); GST_RTSP_STATE_UNLOCK (src); @@ -2728,6 +3369,217 @@ set_manager_buffer_mode (GstRTSPSrc * src) } } +static GstCaps * +request_key (GstElement * srtpdec, guint ssrc, GstRTSPStream * stream) +{ + GST_DEBUG ("request key %u", ssrc); + return gst_caps_ref (stream_get_caps_for_pt (stream, stream->default_pt)); +} + +static GstElement * +request_rtp_decoder (GstElement * rtpbin, guint session, GstRTSPStream * stream) +{ + GST_DEBUG ("decoder session %u, stream %p, %d", session, stream, stream->id); + if (stream->id != session) + return NULL; + + if (stream->profile != GST_RTSP_PROFILE_SAVP && + stream->profile != GST_RTSP_PROFILE_SAVPF) + return NULL; + + if (stream->srtpdec == NULL) { + gchar *name; + + name = g_strdup_printf ("srtpdec_%u", session); + stream->srtpdec = gst_element_factory_make ("srtpdec", name); + g_free (name); + + g_signal_connect (stream->srtpdec, "request-key", + (GCallback) request_key, stream); + } + return gst_object_ref (stream->srtpdec); +} + +static GstElement * +request_rtcp_encoder (GstElement * rtpbin, guint session, + GstRTSPStream * stream) +{ + gchar *name; + GstPad *pad; + + GST_DEBUG ("decoder session %u, stream %p, %d", session, stream, stream->id); + if (stream->id != session) + return NULL; + + if (stream->profile != GST_RTSP_PROFILE_SAVP && + stream->profile != GST_RTSP_PROFILE_SAVPF) + return NULL; + + if (stream->srtpenc == NULL) { + GstStructure *s; + + name = g_strdup_printf ("srtpenc_%u", session); + stream->srtpenc = gst_element_factory_make ("srtpenc", name); + g_free (name); + + /* get RTCP crypto parameters from caps */ + s = gst_caps_get_structure (stream->srtcpparams, 0); + if (s) { + GstBuffer *buf; + const gchar *str; + GType ciphertype, authtype; + GValue rtcp_cipher = G_VALUE_INIT, rtcp_auth = G_VALUE_INIT; + + ciphertype = g_type_from_name ("GstSrtpCipherType"); + authtype = g_type_from_name ("GstSrtpAuthType"); + g_value_init (&rtcp_cipher, ciphertype); + g_value_init (&rtcp_auth, authtype); + + str = gst_structure_get_string (s, "srtcp-cipher"); + gst_value_deserialize (&rtcp_cipher, str); + str = gst_structure_get_string (s, "srtcp-auth"); + gst_value_deserialize (&rtcp_auth, str); + gst_structure_get (s, "srtp-key", GST_TYPE_BUFFER, &buf, NULL); + + g_object_set_property (G_OBJECT (stream->srtpenc), "rtcp-cipher", + &rtcp_cipher); + g_object_set_property (G_OBJECT (stream->srtpenc), "rtcp-auth", + &rtcp_auth); + g_object_set (stream->srtpenc, "key", buf, NULL); + + g_value_unset (&rtcp_cipher); + g_value_unset (&rtcp_auth); + gst_buffer_unref (buf); + } + } + name = g_strdup_printf ("rtcp_sink_%d", session); + pad = gst_element_get_request_pad (stream->srtpenc, name); + g_free (name); + gst_object_unref (pad); + + return gst_object_ref (stream->srtpenc); +} + +static GstElement * +request_aux_receiver (GstElement * rtpbin, guint sessid, GstRTSPSrc * src) +{ + GstElement *rtx, *bin; + GstPad *pad; + gchar *name; + GstRTSPStream *stream; + + stream = find_stream (src, &sessid, (gpointer) find_stream_by_id); + if (!stream) { + GST_WARNING_OBJECT (src, "Stream %u not found", sessid); + return NULL; + } + + GST_INFO_OBJECT (src, "creating retransmision receiver for session %u " + "with map %" GST_PTR_FORMAT, sessid, stream->rtx_pt_map); + bin = gst_bin_new (NULL); + rtx = gst_element_factory_make ("rtprtxreceive", NULL); + g_object_set (rtx, "payload-type-map", stream->rtx_pt_map, NULL); + gst_bin_add (GST_BIN (bin), rtx); + + pad = gst_element_get_static_pad (rtx, "src"); + name = g_strdup_printf ("src_%u", sessid); + gst_element_add_pad (bin, gst_ghost_pad_new (name, pad)); + g_free (name); + gst_object_unref (pad); + + pad = gst_element_get_static_pad (rtx, "sink"); + name = g_strdup_printf ("sink_%u", sessid); + gst_element_add_pad (bin, gst_ghost_pad_new (name, pad)); + g_free (name); + gst_object_unref (pad); + + return bin; +} + +static void +add_retransmission (GstRTSPSrc * src, GstRTSPTransport * transport) +{ + GList *walk; + guint signal_id; + gboolean do_retransmission = FALSE; + + if (transport->trans != GST_RTSP_TRANS_RTP) + return; + if (transport->profile != GST_RTSP_PROFILE_AVPF && + transport->profile != GST_RTSP_PROFILE_SAVPF) + return; + + signal_id = g_signal_lookup ("request-aux-receiver", + G_OBJECT_TYPE (src->manager)); + /* there's already something connected */ + if (g_signal_handler_find (src->manager, G_SIGNAL_MATCH_ID, signal_id, 0, + NULL, NULL, NULL) != 0) { + GST_DEBUG_OBJECT (src, "Not adding RTX AUX element as " + "\"request-aux-receiver\" signal is " + "already used by the application"); + return; + } + + /* build the retransmission payload type map */ + for (walk = src->streams; walk; walk = g_list_next (walk)) { + GstRTSPStream *stream = (GstRTSPStream *) walk->data; + gboolean do_retransmission_stream = FALSE; + int i; + + if (stream->rtx_pt_map) + gst_structure_free (stream->rtx_pt_map); + stream->rtx_pt_map = gst_structure_new_empty ("application/x-rtp-pt-map"); + + for (i = 0; i < stream->ptmap->len; i++) { + PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); + GstStructure *s = gst_caps_get_structure (item->caps, 0); + const gchar *encoding; + + /* we only care about RTX streams */ + if ((encoding = gst_structure_get_string (s, "encoding-name")) + && g_strcmp0 (encoding, "RTX") == 0) { + const gchar *stream_pt_s; + gint rtx_pt; + + if (gst_structure_get_int (s, "payload", &rtx_pt) + && (stream_pt_s = gst_structure_get_string (s, "apt"))) { + + if (rtx_pt != 0) { + gst_structure_set (stream->rtx_pt_map, stream_pt_s, G_TYPE_UINT, + rtx_pt, NULL); + do_retransmission_stream = TRUE; + } + } + } + } + + if (do_retransmission_stream) { + GST_DEBUG_OBJECT (src, "built retransmission payload map for stream " + "id %i: %" GST_PTR_FORMAT, stream->id, stream->rtx_pt_map); + do_retransmission = TRUE; + } else { + GST_DEBUG_OBJECT (src, "no retransmission payload map for stream " + "id %i", stream->id); + gst_structure_free (stream->rtx_pt_map); + stream->rtx_pt_map = NULL; + } + } + + if (do_retransmission) { + GST_DEBUG_OBJECT (src, "Enabling retransmissions"); + + g_object_set (src->manager, "do-retransmission", TRUE, NULL); + + /* enable RFC4588 retransmission handling by setting rtprtxreceive + * as the "aux" element of rtpbin */ + g_signal_connect (src->manager, "request-aux-receiver", + (GCallback) request_aux_receiver, src); + } else { + GST_DEBUG_OBJECT (src, + "Not enabling retransmissions as no stream had a retransmission payload map"); + } +} + /* try to get and configure a manager */ static gboolean gst_rtspsrc_stream_configure_manager (GstRTSPSrc * src, GstRTSPStream * stream, @@ -2747,9 +3599,6 @@ gst_rtspsrc_stream_configure_manager (GstRTSPSrc * src, GstRTSPStream * stream, /* configure the manager */ if (src->manager == NULL) { GObjectClass *klass; - GstStructure *s; - const gchar *encoding; - gboolean need_slave; if (!(src->manager = gst_element_factory_make (manager, "manager"))) { /* fallback */ @@ -2779,9 +3628,15 @@ gst_rtspsrc_stream_configure_manager (GstRTSPSrc * src, GstRTSPStream * stream, g_object_set (src->manager, "ntp-sync", src->ntp_sync, NULL); } - if (g_object_class_find_property (klass, "use-pipeline-clock")) { - g_object_set (src->manager, "use-pipeline-clock", - src->use_pipeline_clock, NULL); + if (src->use_pipeline_clock) { + if (g_object_class_find_property (klass, "use-pipeline-clock")) { + g_object_set (src->manager, "use-pipeline-clock", TRUE, NULL); + } + } else { + if (g_object_class_find_property (klass, "ntp-time-source")) { + g_object_set (src->manager, "ntp-time-source", src->ntp_time_source, + NULL); + } } if (src->sdes && g_object_class_find_property (klass, "sdes")) { @@ -2793,25 +3648,16 @@ gst_rtspsrc_stream_configure_manager (GstRTSPSrc * src, GstRTSPStream * stream, NULL); } - if (g_object_class_find_property (klass, "do-retransmission")) { - g_object_set (src->manager, "do-retransmission", src->do_retransmission, - NULL); - } - /* buffer mode pauses are handled by adding offsets to buffer times, * but some depayloaders may have a hard time syncing output times * with such input times, e.g. container ones, most notably ASF */ /* TODO alternatives are having an event that indicates these shifts, * or having rtsp extensions provide suggestion on buffer mode */ - need_slave = stream->container; - if (stream->caps && (s = gst_caps_get_structure (stream->caps, 0)) && - (encoding = gst_structure_get_string (s, "encoding-name"))) - need_slave = need_slave || (strcmp (encoding, "X-ASF-PF") == 0); /* valid duration implies not likely live pipeline, * so slaving in jitterbuffer does not make much sense * (and might mess things up due to bursts) */ if (GST_CLOCK_TIME_IS_VALID (src->segment.duration) && - src->segment.duration && !need_slave) { + src->segment.duration && stream->container) { src->use_buffering = TRUE; } else { src->use_buffering = FALSE; @@ -2834,7 +3680,16 @@ gst_rtspsrc_stream_configure_manager (GstRTSPSrc * src, GstRTSPStream * stream, g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_NEW_MANAGER], 0, src->manager); + + if (src->do_retransmission) + add_retransmission (src, transport); } + g_signal_connect (src->manager, "request-rtp-decoder", + (GCallback) request_rtp_decoder, stream); + g_signal_connect (src->manager, "request-rtcp-decoder", + (GCallback) request_rtp_decoder, stream); + g_signal_connect (src->manager, "request-rtcp-encoder", + (GCallback) request_rtcp_encoder, stream); /* we stream directly to the manager, get some pads. Each RTSP stream goes * into a separate RTP session. */ @@ -2853,6 +3708,8 @@ gst_rtspsrc_stream_configure_manager (GstRTSPSrc * src, GstRTSPStream * stream, g_signal_emit_by_name (src->manager, "get-internal-session", stream->id, &rtpsession); if (rtpsession) { + GstRTPProfile rtp_profile; + GST_INFO_OBJECT (src, "configure bandwidth in session %p", rtpsession); stream->session = rtpsession; @@ -2874,8 +3731,28 @@ gst_rtspsrc_stream_configure_manager (GstRTSPSrc * src, GstRTSPStream * stream, NULL); } + switch (stream->profile) { + case GST_RTSP_PROFILE_AVPF: + rtp_profile = GST_RTP_PROFILE_AVPF; + break; + case GST_RTSP_PROFILE_SAVP: + rtp_profile = GST_RTP_PROFILE_SAVP; + break; + case GST_RTSP_PROFILE_SAVPF: + rtp_profile = GST_RTP_PROFILE_SAVPF; + break; + case GST_RTSP_PROFILE_AVP: + default: + rtp_profile = GST_RTP_PROFILE_AVP; + break; + } + + g_object_set (rtpsession, "rtp-profile", rtp_profile, NULL); + g_object_set (rtpsession, "probation", src->probation, NULL); + g_object_set (rtpsession, "internal-ssrc", stream->send_ssrc, NULL); + g_signal_connect (rtpsession, "on-bye-ssrc", (GCallback) on_bye_ssrc, stream); g_signal_connect (rtpsession, "on-bye-timeout", (GCallback) on_timeout, @@ -3117,7 +3994,7 @@ gst_rtspsrc_stream_configure_mcast (GstRTSPSrc * src, GstRTSPStream * stream, /* change state */ gst_element_set_locked_state (stream->udpsrc[0], TRUE); - gst_element_set_state (stream->udpsrc[0], GST_STATE_PAUSED); + gst_element_set_state (stream->udpsrc[0], GST_STATE_READY); } /* creating another UDP source for RTCP */ @@ -3131,7 +4008,11 @@ gst_rtspsrc_stream_configure_mcast (GstRTSPSrc * src, GstRTSPStream * stream, if (stream->udpsrc[1] == NULL) goto no_element; - caps = gst_caps_new_empty_simple ("application/x-rtcp"); + if (stream->profile == GST_RTSP_PROFILE_SAVP || + stream->profile == GST_RTSP_PROFILE_SAVPF) + caps = gst_caps_new_empty_simple ("application/x-srtcp"); + else + caps = gst_caps_new_empty_simple ("application/x-rtcp"); g_object_set (stream->udpsrc[1], "caps", caps, NULL); gst_caps_unref (caps); @@ -3142,7 +4023,7 @@ gst_rtspsrc_stream_configure_mcast (GstRTSPSrc * src, GstRTSPStream * stream, g_object_set (G_OBJECT (stream->udpsrc[0]), "multicast-iface", src->multi_iface, NULL); - gst_element_set_state (stream->udpsrc[1], GST_STATE_PAUSED); + gst_element_set_state (stream->udpsrc[1], GST_STATE_READY); } return TRUE; @@ -3172,6 +4053,8 @@ gst_rtspsrc_stream_configure_udp (GstRTSPSrc * src, GstRTSPStream * stream, /* we manage the UDP elements now. For unicast, the UDP sources where * allocated in the stream when we suggested a transport. */ if (stream->udpsrc[0]) { + GstCaps *caps; + gst_element_set_locked_state (stream->udpsrc[0], TRUE); gst_bin_add (GST_BIN_CAST (src), stream->udpsrc[0]); @@ -3183,6 +4066,9 @@ gst_rtspsrc_stream_configure_udp (GstRTSPSrc * src, GstRTSPStream * stream, g_object_set (G_OBJECT (stream->udpsrc[0]), "timeout", src->udp_timeout * 1000, NULL); + if ((caps = stream_get_caps_for_pt (stream, stream->default_pt))) + g_object_set (stream->udpsrc[0], "caps", caps, NULL); + /* get output pad of the UDP source. */ *outpad = gst_element_get_static_pad (stream->udpsrc[0], "src"); @@ -3194,7 +4080,8 @@ gst_rtspsrc_stream_configure_udp (GstRTSPSrc * src, GstRTSPStream * stream, * configure all the streams to let the application autoplug decoders. */ stream->blockid = gst_pad_add_probe (stream->blockedpad, - GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, pad_blocked, src, NULL); + GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER | + GST_PAD_PROBE_TYPE_BUFFER_LIST, pad_blocked, src, NULL); if (stream->channelpad[0]) { GST_DEBUG_OBJECT (src, "connecting UDP source 0 to manager"); @@ -3217,7 +4104,11 @@ gst_rtspsrc_stream_configure_udp (GstRTSPSrc * src, GstRTSPStream * stream, gst_element_set_locked_state (stream->udpsrc[1], TRUE); gst_bin_add (GST_BIN_CAST (src), stream->udpsrc[1]); - caps = gst_caps_new_empty_simple ("application/x-rtcp"); + if (stream->profile == GST_RTSP_PROFILE_SAVP || + stream->profile == GST_RTSP_PROFILE_SAVPF) + caps = gst_caps_new_empty_simple ("application/x-srtcp"); + else + caps = gst_caps_new_empty_simple ("application/x-rtcp"); g_object_set (stream->udpsrc[1], "caps", caps, NULL); gst_caps_unref (caps); @@ -3408,15 +4299,13 @@ gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream, GstPad *outpad = NULL; GstPadTemplate *template; gchar *name; - GstStructure *s; const gchar *media_type; + guint i, len; src = stream->parent; GST_DEBUG_OBJECT (src, "configuring transport for stream %p", stream); - s = gst_caps_get_structure (stream->caps, 0); - /* get the proper media type for this stream now */ if (gst_rtsp_transport_get_media_type (transport, &media_type) < 0) goto unknown_transport; @@ -3425,7 +4314,21 @@ gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream, /* configure the final media type */ GST_DEBUG_OBJECT (src, "setting media type to %s", media_type); - gst_structure_set_name (s, media_type); + + len = stream->ptmap->len; + for (i = 0; i < len; i++) { + GstStructure *s; + PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); + + if (item->caps == NULL) + continue; + + s = gst_caps_get_structure (item->caps, 0); + gst_structure_set_name (s, media_type); + /* set ssrc if known */ + if (transport->ssrc) + gst_structure_set (s, "ssrc", G_TYPE_UINT, transport->ssrc, NULL); + } /* try to get and configure a manager, channelpad[0-1] will be configured with * the pads for the manager, or NULL when no manager is needed. */ @@ -3546,8 +4449,11 @@ gst_rtspsrc_activate_streams (GstRTSPSrc * src) /* if we don't have a session manager, set the caps now. If we have a * session, we will get a notification of the pad and the caps. */ if (!src->manager) { + GstCaps *caps; + + caps = stream_get_caps_for_pt (stream, stream->default_pt); GST_DEBUG_OBJECT (src, "setting pad caps for stream %p", stream); - gst_pad_set_caps (stream->srcpad, stream->caps); + gst_pad_set_caps (stream->srcpad, caps); } /* add the pad */ if (!stream->added) { @@ -3589,10 +4495,20 @@ gst_rtspsrc_configure_caps (GstRTSPSrc * src, GstSegment * segment, for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; - GstCaps *caps; + guint j, len; + + if (!stream->setup) + continue; + + len = stream->ptmap->len; + for (j = 0; j < len; j++) { + GstCaps *caps; + PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, j); - if ((caps = stream->caps)) { - caps = gst_caps_make_writable (caps); + if (item->caps == NULL) + continue; + + caps = gst_caps_make_writable (item->caps); /* update caps */ if (stream->timebase != -1) gst_caps_set_simple (caps, "clock-base", G_TYPE_UINT, @@ -3606,9 +4522,14 @@ gst_rtspsrc_configure_caps (GstRTSPSrc * src, GstSegment * segment, gst_caps_set_simple (caps, "play-speed", G_TYPE_DOUBLE, play_speed, NULL); gst_caps_set_simple (caps, "play-scale", G_TYPE_DOUBLE, play_scale, NULL); - stream->caps = caps; + item->caps = caps; + GST_DEBUG_OBJECT (src, "stream %p, pt %d, caps %" GST_PTR_FORMAT, stream, + item->pt, caps); + + if (item->pt == stream->default_pt && stream->udpsrc[0]) { + g_object_set (stream->udpsrc[0], "caps", caps, NULL); + } } - GST_DEBUG_OBJECT (src, "stream %p, caps %" GST_PTR_FORMAT, stream, caps); } if (reset_manager && src->manager) { GST_DEBUG_OBJECT (src, "clear session"); @@ -3657,7 +4578,7 @@ gst_rtspsrc_stream_push_event (GstRTSPSrc * src, GstRTSPStream * stream, gboolean res = TRUE; /* only streams that have a connection to the outside world */ - if (stream->container || stream->disabled) + if (!stream->setup) goto done; if (stream->udpsrc[0]) { @@ -3733,6 +4654,14 @@ gst_rtsp_conninfo_connect (GstRTSPSrc * src, GstRTSPConnInfo * info, if (!gst_rtsp_connection_set_tls_validation_flags (info->connection, src->tls_validation_flags)) GST_WARNING_OBJECT (src, "Unable to set TLS validation flags"); + + if (src->tls_database) + gst_rtsp_connection_set_tls_database (info->connection, + src->tls_database); + + if (src->tls_interaction) + gst_rtsp_connection_set_tls_interaction (info->connection, + src->tls_interaction); } if (info->url->transports & GST_RTSP_LOWER_TRANS_HTTP) @@ -3839,6 +4768,23 @@ gst_rtspsrc_connection_flush (GstRTSPSrc * src, gboolean flush) GST_RTSP_STATE_UNLOCK (src); } +static GstRTSPResult +gst_rtspsrc_init_request (GstRTSPSrc * src, GstRTSPMessage * msg, + GstRTSPMethod method, const gchar * uri) +{ + GstRTSPResult res; + + res = gst_rtsp_message_init_request (msg, method, uri); + if (res < 0) + return res; + + /* set user-agent */ + if (src->user_agent) + gst_rtsp_message_add_header (msg, GST_RTSP_HDR_USER_AGENT, src->user_agent); + + return res; +} + /* FIXME, handle server request, reply with OK, for now */ static GstRTSPResult gst_rtspsrc_handle_request (GstRTSPSrc * src, GstRTSPConnection * conn, @@ -3849,8 +4795,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); @@ -3867,8 +4812,7 @@ 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); if (res < 0) @@ -3915,12 +4859,11 @@ gst_rtspsrc_send_keep_alive (GstRTSPSrc * src) if (control == NULL) goto no_control; - res = gst_rtsp_message_init_request (&request, method, control); + res = gst_rtspsrc_init_request (src, &request, method, control); if (res < 0) goto send_error; - if (src->debug) - gst_rtsp_message_dump (&request); + DEBUG_RTSP (src, &request); res = gst_rtspsrc_connection_send (src, src->conninfo.connection, &request, @@ -3962,7 +4905,6 @@ gst_rtspsrc_handle_data (GstRTSPSrc * src, GstRTSPMessage * message) guint size; GstBuffer *buf; gboolean is_rtcp; - GstEvent *event; channel = message->type_data.data.channel; @@ -4027,6 +4969,7 @@ gst_rtspsrc_handle_data (GstRTSPSrc * src, GstRTSPMessage * message) for (streams = src->streams; streams; streams = g_list_next (streams)) { GstRTSPStream *ostream = (GstRTSPStream *) streams->data; + GstCaps *caps; stream_id = g_strdup_printf ("%s/%d", g_checksum_get_string (cs), ostream->id); @@ -4035,15 +4978,45 @@ gst_rtspsrc_handle_data (GstRTSPSrc * src, GstRTSPMessage * message) g_free (stream_id); gst_rtspsrc_stream_push_event (src, ostream, event); + + if ((caps = stream_get_caps_for_pt (ostream, ostream->default_pt))) { + /* only streams that have a connection to the outside world */ + if (ostream->setup) { + if (ostream->udpsrc[0]) { + gst_element_send_event (ostream->udpsrc[0], + gst_event_new_caps (caps)); + } else if (ostream->channelpad[0]) { + if (GST_PAD_IS_SRC (ostream->channelpad[0])) + gst_pad_push_event (ostream->channelpad[0], + gst_event_new_caps (caps)); + else + gst_pad_send_event (ostream->channelpad[0], + gst_event_new_caps (caps)); + } + + caps = gst_caps_new_empty_simple ("application/x-rtcp"); + + if (ostream->udpsrc[1]) { + gst_element_send_event (ostream->udpsrc[1], + gst_event_new_caps (caps)); + } else if (ostream->channelpad[1]) { + if (GST_PAD_IS_SRC (ostream->channelpad[1])) + gst_pad_push_event (ostream->channelpad[1], + gst_event_new_caps (caps)); + else + gst_pad_send_event (ostream->channelpad[1], + gst_event_new_caps (caps)); + } + + gst_caps_unref (caps); + } + } } g_checksum_free (cs); gst_rtspsrc_activate_streams (src); src->need_activate = FALSE; - } - if ((event = src->start_segment) != NULL) { - src->start_segment = NULL; - gst_rtspsrc_push_event (src, event); + src->need_segment = TRUE; } if (src->base_time == -1) { @@ -4070,6 +5043,15 @@ gst_rtspsrc_handle_data (GstRTSPSrc * src, GstRTSPMessage * message) GST_OBJECT_UNLOCK (src); } + /* If needed send a new segment, don't forget we are live and buffer are + * timestamped with running time */ + if (src->need_segment) { + GstSegment segment; + src->need_segment = FALSE; + gst_segment_init (&segment, GST_FORMAT_TIME); + gst_rtspsrc_push_event (src, gst_event_new_segment (&segment)); + } + if (stream->discont && !is_rtcp) { /* mark first RTP buffer as discont */ GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); @@ -4176,8 +5158,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"); @@ -4213,8 +5194,13 @@ receive_error: { gchar *str = gst_rtsp_strresult (res); +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_BAD_SERVER, + "Could not receive message."); +#else GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), ("Could not receive message. (%s)", str)); +#endif g_free (str); gst_rtsp_message_unset (&message); @@ -4224,8 +5210,13 @@ handle_request_failed: { gchar *str = gst_rtsp_strresult (res); +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_SERVICE_UNAVAILABLE, + "Could not handle server message."); +#else GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Could not handle server message. (%s)", str)); +#endif g_free (str); gst_rtsp_message_unset (&message); return GST_FLOW_ERROR; @@ -4309,8 +5300,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++)) { @@ -4348,8 +5338,13 @@ connect_error: src->conninfo.connected = FALSE; if (res != GST_RTSP_EINTR) { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_CONNECTION_FAIL, + "Could not connect to server."); +#else GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ_WRITE, (NULL), ("Could not connect to server. (%s)", str)); +#endif g_free (str); ret = GST_FLOW_ERROR; } else { @@ -4361,8 +5356,13 @@ receive_error: { gchar *str = gst_rtsp_strresult (res); +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_SERVER_DISCONNECTED, + "Could not receive message."); +#else GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), ("Could not receive message. (%s)", str)); +#endif g_free (str); return GST_FLOW_ERROR; } @@ -4373,8 +5373,14 @@ handle_request_failed: gst_rtsp_message_unset (&message); if (res != GST_RTSP_EINTR) { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, + GST_RTSPSRC_ERROR_SERVICE_UNAVAILABLE, + "Could not handle server message."); +#else GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Could not handle server message. (%s)", str)); +#endif g_free (str); ret = GST_FLOW_ERROR; } else { @@ -4446,10 +5452,15 @@ no_protocols: { src->cur_protocols = 0; /* no transport possible, post an error and stop */ +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_BAD_TRANSPORT, + "Could not receive any UDP packets for seconds, maybe your firewall is blocking it. No other protocols to try."); +#else 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))); +#endif return GST_RTSP_ERROR; } open_failed: @@ -4488,9 +5499,38 @@ gst_rtspsrc_loop_start_cmd (GstRTSPSrc * src, gint cmd) static void gst_rtspsrc_loop_complete_cmd (GstRTSPSrc * src, gint cmd) { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + GstMessage *s; +#endif + GST_WARNING_OBJECT (src, "Got cmd %s", cmd_to_string (cmd)); + switch (cmd) { case CMD_OPEN: +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + GST_DEBUG_OBJECT (src, + "rtsp_duration %" GST_TIME_FORMAT + ", rtsp_audio_codec %s , rtsp_video_codec %s , rtsp_video_frame_size %s", + GST_TIME_ARGS (src->segment.duration), src->audio_codec, + src->video_codec, src->video_frame_size); + + /* post message */ + s = gst_message_new_element (GST_OBJECT_CAST (src), + gst_structure_new ("rtspsrc_properties", + "rtsp_duration", G_TYPE_UINT64, src->segment.duration, + "rtsp_audio_codec", G_TYPE_STRING, src->audio_codec, + "rtsp_video_codec", G_TYPE_STRING, src->video_codec, + "rtsp_video_frame_size", G_TYPE_STRING, src->video_frame_size, + NULL)); + + gst_element_post_message (GST_ELEMENT_CAST (src), s); +#endif GST_ELEMENT_PROGRESS (src, COMPLETE, "open", ("Opened Stream")); +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + /* rtspsrc PAUSE state should be here for parsing sdp before PAUSE state changed. */ + g_mutex_lock (&(src)->pause_lock); + g_cond_signal (&(src)->open_end); + g_mutex_unlock (&(src)->pause_lock); +#endif break; case CMD_PLAY: GST_ELEMENT_PROGRESS (src, COMPLETE, "request", ("Sent PLAY request")); @@ -4533,6 +5573,14 @@ gst_rtspsrc_loop_error_cmd (GstRTSPSrc * src, gint cmd) switch (cmd) { case CMD_OPEN: GST_ELEMENT_PROGRESS (src, ERROR, "open", ("Open failed")); +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + /* Ending conditional wait for pause when open fails.*/ + g_mutex_lock (&(src)->pause_lock); + g_cond_signal (&(src)->open_end); + g_mutex_unlock (&(src)->pause_lock); + GST_WARNING_OBJECT (src, + "ending conditional wait for pause as open is failed."); +#endif break; case CMD_PLAY: GST_ELEMENT_PROGRESS (src, ERROR, "request", ("PLAY failed")); @@ -4568,7 +5616,7 @@ gst_rtspsrc_loop_send_cmd (GstRTSPSrc * src, gint cmd, gint mask) /* start new request */ gst_rtspsrc_loop_start_cmd (src, cmd); - GST_DEBUG_OBJECT (src, "sending cmd %d", cmd); + GST_DEBUG_OBJECT (src, "sending cmd %s", cmd_to_string (cmd)); GST_OBJECT_LOCK (src); old = src->pending_cmd; @@ -4580,18 +5628,20 @@ gst_rtspsrc_loop_send_cmd (GstRTSPSrc * src, gint cmd, gint mask) src->pending_cmd = CMD_WAIT; GST_OBJECT_UNLOCK (src); /* cancel previous request */ - GST_DEBUG_OBJECT (src, "cancel previous request %d", old); + GST_DEBUG_OBJECT (src, "cancel previous request %s", cmd_to_string (old)); gst_rtspsrc_loop_cancel_cmd (src, old); GST_OBJECT_LOCK (src); } src->pending_cmd = cmd; /* interrupt if allowed */ if (src->busy_cmd & mask) { - GST_DEBUG_OBJECT (src, "connection flush busy %d", src->busy_cmd); + GST_DEBUG_OBJECT (src, "connection flush busy %s", + cmd_to_string (src->busy_cmd)); gst_rtspsrc_connection_flush (src, TRUE); flushed = TRUE; } else { - GST_DEBUG_OBJECT (src, "not interrupting busy cmd %d", src->busy_cmd); + GST_DEBUG_OBJECT (src, "not interrupting busy cmd %s", + cmd_to_string (src->busy_cmd)); } if (src->task) gst_task_start (src->task); @@ -4795,8 +5845,7 @@ gst_rtspsrc_parse_digest_challenge (GstRTSPConnection * conn, } else value = NULL; - if (item && (strcmp (item, "stale") == 0) && - value && (strcmp (value, "TRUE") == 0)) + if (value && strcmp (item, "stale") == 0 && strcmp (value, "TRUE") == 0) *stale = TRUE; gst_rtsp_connection_set_auth_param (conn, item, value); g_free (item); @@ -4930,8 +5979,13 @@ no_auth_available: { /* Output an error indicating that we couldn't connect because there were * no supported authentication protocols */ +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_NOT_AUTHORIZED, + "No supported authentication protocol was found"); +#else GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), ("No supported authentication protocol was found")); +#endif return FALSE; } no_user_pass: @@ -4958,8 +6012,7 @@ again: GST_DEBUG_OBJECT (src, "sending message"); - if (src->debug) - gst_rtsp_message_dump (request); + DEBUG_RTSP (src, request); res = gst_rtspsrc_connection_send (src, conn, request, src->ptcp_timeout); if (res < 0) @@ -4972,8 +6025,7 @@ next: 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: @@ -5027,8 +6079,13 @@ send_error: gchar *str = gst_rtsp_strresult (res); if (res != GST_RTSP_EINTR) { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_CONNECTION_FAIL, + "Could not send message."); +#else GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Could not send message. (%s)", str)); +#endif } else { GST_WARNING_OBJECT (src, "send interrupted"); } @@ -5054,8 +6111,14 @@ receive_error: gchar *str = gst_rtsp_strresult (res); if (res != GST_RTSP_EINTR) { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, + GST_RTSPSRC_ERROR_SERVER_DISCONNECTED, + "Could not receive message."); +#else GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), ("Could not receive message. (%s)", str)); +#endif } else { GST_WARNING_OBJECT (src, "receive interrupted"); } @@ -5132,6 +6195,7 @@ gst_rtspsrc_send (GstRTSPSrc * src, GstRTSPConnection * conn, switch (int_code) { case GST_RTSP_STS_UNAUTHORIZED: + case GST_RTSP_STS_NOT_FOUND: if (gst_rtspsrc_setup_auth (src, response)) { /* Try the request/response again after configuring the auth info * and loop again */ @@ -5164,8 +6228,22 @@ error_response: switch (response->type_data.response.code) { case GST_RTSP_STS_NOT_FOUND: +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_BAD_REQUEST, + "STS NOT FOUND"); +#else GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL), ("%s", response->type_data.response.reason)); +#endif + break; + case GST_RTSP_STS_UNAUTHORIZED: +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_NOT_AUTHORIZED, + "STS NOT AUTHORIZED"); +#else + GST_ELEMENT_ERROR (src, RESOURCE, NOT_AUTHORIZED, (NULL), ("%s", + response->type_data.response.reason)); +#endif break; case GST_RTSP_STS_MOVED_PERMANENTLY: case GST_RTSP_STS_MOVE_TEMPORARILY: @@ -5210,9 +6288,14 @@ error_response: res = GST_RTSP_OK; break; default: +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_UNEXPECTED_MSG, + "Got error response from Server"); +#else GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), ("Got error response: %d (%s).", response->type_data.response.code, response->type_data.response.reason)); +#endif break; } /* if we return ERROR we should unset the response ourselves */ @@ -5288,21 +6371,31 @@ gst_rtspsrc_parse_methods (GstRTSPSrc * src, GstRTSPMessage * response) /* ERRORS */ no_describe: { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_METHOD_NOT_ALLOWED, + "Server does not support DESCRIBE."); +#else GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), ("Server does not support DESCRIBE.")); +#endif return FALSE; } no_setup: { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_METHOD_NOT_ALLOWED, + "Server does not support SETUP."); +#else GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), ("Server does not support SETUP.")); +#endif return FALSE; } } /* masks to be kept in sync with the hardcoded protocol order of preference * in code below */ -static guint protocol_masks[] = { +static const guint protocol_masks[] = { GST_RTSP_LOWER_TRANS_UDP, GST_RTSP_LOWER_TRANS_UDP_MCAST, GST_RTSP_LOWER_TRANS_TCP, @@ -5311,7 +6404,7 @@ static guint protocol_masks[] = { static GstRTSPResult gst_rtspsrc_create_transports_string (GstRTSPSrc * src, - GstRTSPLowerTrans protocols, gchar ** transports) + GstRTSPLowerTrans protocols, GstRTSPProfile profile, gchar ** transports) { GstRTSPResult res; GString *result; @@ -5335,23 +6428,35 @@ gst_rtspsrc_create_transports_string (GstRTSPSrc * src, add_udp_str = FALSE; /* the default RTSP transports */ - result = g_string_new (""); + result = g_string_new ("RTP"); + + switch (profile) { + case GST_RTSP_PROFILE_AVP: + g_string_append (result, "/AVP"); + break; + case GST_RTSP_PROFILE_SAVP: + g_string_append (result, "/SAVP"); + break; + case GST_RTSP_PROFILE_AVPF: + g_string_append (result, "/AVPF"); + break; + case GST_RTSP_PROFILE_SAVPF: + g_string_append (result, "/SAVPF"); + break; + default: + break; + } + if (protocols & GST_RTSP_LOWER_TRANS_UDP) { GST_DEBUG_OBJECT (src, "adding UDP unicast"); - - g_string_append (result, "RTP/AVP"); if (add_udp_str) g_string_append (result, "/UDP"); g_string_append (result, ";unicast;client_port=%%u1-%%u2"); } else if (protocols & GST_RTSP_LOWER_TRANS_UDP_MCAST) { GST_DEBUG_OBJECT (src, "adding UDP multicast"); - /* we don't have to allocate any UDP ports yet, if the selected transport * turns out to be multicast we can create them and join the multicast * group indicated in the transport reply */ - if (result->len > 0) - g_string_append (result, ","); - g_string_append (result, "RTP/AVP"); if (add_udp_str) g_string_append (result, "/UDP"); g_string_append (result, ";multicast"); @@ -5366,9 +6471,7 @@ gst_rtspsrc_create_transports_string (GstRTSPSrc * src, } else if (protocols & GST_RTSP_LOWER_TRANS_TCP) { GST_DEBUG_OBJECT (src, "adding TCP"); - if (result->len > 0) - g_string_append (result, ","); - g_string_append (result, "RTP/AVP/TCP;unicast;interleaved=%%i1-%%i2"); + g_string_append (result, "/TCP;unicast;interleaved=%%i1-%%i2"); } *transports = g_string_free (result, FALSE); @@ -5465,23 +6568,173 @@ failed: } } -static gboolean -gst_rtspsrc_stream_is_real_media (GstRTSPStream * stream) +static guint8 +enc_key_length_from_cipher_name (const gchar * cipher) { - gboolean res = FALSE; + if (g_strcmp0 (cipher, "aes-128-icm") == 0) + return AES_128_KEY_LEN; + else if (g_strcmp0 (cipher, "aes-256-icm") == 0) + return AES_256_KEY_LEN; + else { + GST_ERROR ("encryption algorithm '%s' not supported", cipher); + return 0; + } +} - if (stream->caps) { - GstStructure *s; - const gchar *enc = NULL; +static guint8 +auth_key_length_from_auth_name (const gchar * auth) +{ + if (g_strcmp0 (auth, "hmac-sha1-32") == 0) + return HMAC_32_KEY_LEN; + else if (g_strcmp0 (auth, "hmac-sha1-80") == 0) + return HMAC_80_KEY_LEN; + else { + GST_ERROR ("authentication algorithm '%s' not supported", auth); + return 0; + } +} - s = gst_caps_get_structure (stream->caps, 0); - if ((enc = gst_structure_get_string (s, "encoding-name"))) { - res = (strstr (enc, "-REAL") != NULL); - } +static GstCaps * +signal_get_srtcp_params (GstRTSPSrc * src, GstRTSPStream * stream) +{ + GstCaps *caps = NULL; + + g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_REQUEST_RTCP_KEY], 0, + stream->id, &caps); + + if (caps != NULL) + GST_DEBUG_OBJECT (src, "SRTP parameters received"); + + return caps; +} + +static GstCaps * +default_srtcp_params (void) +{ + guint i; + GstCaps *caps; + GstBuffer *buf; + guint8 *key_data; +#define KEY_SIZE 30 + guint data_size = GST_ROUND_UP_4 (KEY_SIZE); + + /* create a random key */ + key_data = g_malloc (data_size); + for (i = 0; i < data_size; i += 4) + GST_WRITE_UINT32_BE (key_data + i, g_random_int ()); + + buf = gst_buffer_new_wrapped (key_data, KEY_SIZE); + + caps = gst_caps_new_simple ("application/x-srtp", + "srtp-key", GST_TYPE_BUFFER, buf, + "srtcp-cipher", G_TYPE_STRING, "aes-128-icm", + "srtcp-auth", G_TYPE_STRING, "hmac-sha1-80", NULL); + + gst_buffer_unref (buf); + + return caps; +} + +static gchar * +gst_rtspsrc_stream_make_keymgmt (GstRTSPSrc * src, GstRTSPStream * stream) +{ + GBytes *bytes; + gchar *result, *base64; + const guint8 *data; + gsize size; + GstMIKEYMessage *msg; + GstMIKEYPayload *payload, *pkd; + guint8 byte; + GstStructure *s; + GstMapInfo info; + GstBuffer *srtpkey; + const GValue *val; + const gchar *srtcpcipher, *srtcpauth; + + stream->srtcpparams = signal_get_srtcp_params (src, stream); + if (stream->srtcpparams == NULL) + stream->srtcpparams = default_srtcp_params (); + + s = gst_caps_get_structure (stream->srtcpparams, 0); + + srtcpcipher = gst_structure_get_string (s, "srtcp-cipher"); + srtcpauth = gst_structure_get_string (s, "srtcp-auth"); + val = gst_structure_get_value (s, "srtp-key"); + + if (srtcpcipher == NULL || srtcpauth == NULL || val == NULL) { + GST_ERROR_OBJECT (src, "could not find the right SRTP parameters in caps"); + return NULL; } - return res; + + srtpkey = gst_value_get_buffer (val); + + msg = gst_mikey_message_new (); + /* unencrypted MIKEY message, we send this over TLS so this is allowed */ + gst_mikey_message_set_info (msg, GST_MIKEY_VERSION, GST_MIKEY_TYPE_PSK_INIT, + FALSE, GST_MIKEY_PRF_MIKEY_1, g_random_int (), GST_MIKEY_MAP_TYPE_SRTP); + /* add policy '0' for our SSRC */ + gst_mikey_message_add_cs_srtp (msg, 0, stream->send_ssrc, 0); + /* timestamp is now */ + gst_mikey_message_add_t_now_ntp_utc (msg); + /* add some random data */ + gst_mikey_message_add_rand_len (msg, 16); + + /* the policy '0' is SRTP */ + payload = gst_mikey_payload_new (GST_MIKEY_PT_SP); + gst_mikey_payload_sp_set (payload, 0, GST_MIKEY_SEC_PROTO_SRTP); + + /* only AES-CM is supported */ + byte = 1; + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_ENC_ALG, 1, &byte); + /* encryption key length */ + byte = enc_key_length_from_cipher_name (srtcpcipher); + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_ENC_KEY_LEN, 1, + &byte); + /* only HMAC-SHA1 */ + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_AUTH_ALG, 1, + &byte); + /* authentication key length */ + byte = auth_key_length_from_auth_name (srtcpauth); + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_AUTH_KEY_LEN, 1, + &byte); + /* we enable encryption on RTP and RTCP */ + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTP_ENC, 1, + &byte); + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTCP_ENC, 1, + &byte); + /* we enable authentication on RTP and RTCP */ + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTP_AUTH, 1, + &byte); + gst_mikey_message_add_payload (msg, payload); + + /* make unencrypted KEMAC */ + payload = gst_mikey_payload_new (GST_MIKEY_PT_KEMAC); + gst_mikey_payload_kemac_set (payload, GST_MIKEY_ENC_NULL, GST_MIKEY_MAC_NULL); + /* add the key in KEMAC */ + pkd = gst_mikey_payload_new (GST_MIKEY_PT_KEY_DATA); + gst_buffer_map (srtpkey, &info, GST_MAP_READ); + gst_mikey_payload_key_data_set_key (pkd, GST_MIKEY_KD_TEK, info.size, + info.data); + gst_buffer_unmap (srtpkey, &info); + gst_mikey_payload_kemac_add_sub (payload, pkd); + gst_mikey_message_add_payload (msg, payload); + + /* now serialize this to bytes */ + bytes = gst_mikey_message_to_bytes (msg, NULL, NULL); + gst_mikey_message_unref (msg); + /* and make it into base64 */ + data = g_bytes_get_data (bytes, &size); + base64 = g_base64_encode (data, size); + g_bytes_unref (bytes); + + result = g_strdup_printf ("prot=mikey;uri=\"%s\";data=\"%s\"", + stream->conninfo.location, base64); + g_free (base64); + + return result; } + /* Perform the SETUP request for all the streams. * * We ask the server for a specific transport, which initially includes all the @@ -5539,32 +6792,41 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) gint retry = 0; guint mask = 0; gboolean selected; + GstCaps *caps; stream = (GstRTSPStream *) walk->data; + caps = stream_get_caps_for_pt (stream, stream->default_pt); + if (caps == NULL) { + GST_DEBUG_OBJECT (src, "skipping stream %p, no caps", stream); + continue; + } + + if (stream->skipped) { + GST_DEBUG_OBJECT (src, "skipping stream %p", stream); + continue; + } + /* see if we need to configure this stream */ - if (!gst_rtsp_ext_list_configure_stream (src->extensions, stream->caps)) { + if (!gst_rtsp_ext_list_configure_stream (src->extensions, caps)) { GST_DEBUG_OBJECT (src, "skipping stream %p, disabled by extension", stream); - stream->disabled = TRUE; continue; } g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_SELECT_STREAM], 0, - stream->id, stream->caps, &selected); + stream->id, caps, &selected); if (!selected) { GST_DEBUG_OBJECT (src, "skipping stream %p, disabled by signal", stream); - stream->disabled = TRUE; continue; } - stream->disabled = FALSE; /* merge/overwrite global caps */ - if (stream->caps) { + if (caps) { guint j, num; GstStructure *s; - s = gst_caps_get_structure (stream->caps, 0); + s = gst_caps_get_structure (caps, 0); num = gst_structure_n_fields (src->props); for (j = 0; j < num; j++) { @@ -5614,7 +6876,7 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) /* create a string with first transport in line */ transports = NULL; res = gst_rtspsrc_create_transports_string (src, - protocols & protocol_masks[mask], &transports); + protocols & protocol_masks[mask], stream->profile, &transports); if (res < 0 || transports == NULL) goto setup_transport_failed; @@ -5640,7 +6902,7 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) /* create SETUP request */ res = - gst_rtsp_message_init_request (&request, GST_RTSP_SETUP, + gst_rtspsrc_init_request (src, &request, GST_RTSP_SETUP, stream->conninfo.location); if (res < 0) { g_free (transports); @@ -5650,6 +6912,13 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) /* select transport */ gst_rtsp_message_take_header (&request, GST_RTSP_HDR_TRANSPORT, transports); + /* set up keys */ + if (stream->profile == GST_RTSP_PROFILE_SAVP || + stream->profile == GST_RTSP_PROFILE_SAVPF) { + hval = gst_rtspsrc_stream_make_keymgmt (src, stream); + gst_rtsp_message_take_header (&request, GST_RTSP_HDR_KEYMGMT, hval); + } + /* if the user wants a non default RTP packet size we add the blocksize * parameter */ if (src->rtp_blocksize > 0) { @@ -5662,7 +6931,8 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) stream->id)); /* handle the code ourselves */ - if ((res = gst_rtspsrc_send (src, conn, &request, &response, &code) < 0)) + res = gst_rtspsrc_send (src, conn, &request, &response, &code); + if (res < 0) goto send_error; switch (code) { @@ -5686,7 +6956,7 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) * but not without checking for lost cause/extension so we can * post a nicer/more useful error message later */ if (!unsupported_real) - unsupported_real = gst_rtspsrc_stream_is_real_media (stream); + unsupported_real = stream->is_real; /* select next available protocol, give up on this stream if none */ mask++; while (protocol_masks[mask] && !(protocols & protocol_masks[mask])) @@ -5756,7 +7026,7 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) break; } - if (!stream->container || (!src->interleaved && !retry)) { + if (!src->interleaved || !retry) { /* now configure the stream with the selected transport */ if (!gst_rtspsrc_stream_configure_transport (stream, &transport)) { GST_DEBUG_OBJECT (src, @@ -5771,6 +7041,29 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) } /* we need to activate at least one streams when we detect activity */ src->need_activate = TRUE; + + /* stream is setup now */ + stream->setup = TRUE; + { + GList *skip = walk; + + while (TRUE) { + GstRTSPStream *sskip; + + skip = g_list_next (skip); + if (skip == NULL) + break; + + sskip = (GstRTSPStream *) skip->data; + + /* skip all streams with the same control url */ + if (g_str_equal (stream->conninfo.location, sskip->conninfo.location)) { + GST_DEBUG_OBJECT (src, "found stream %p with same control %s", + sskip, sskip->conninfo.location); + sskip->skipped = TRUE; + } + } + } next: /* clean up our transport struct */ gst_rtsp_transport_init (&transport); @@ -5794,39 +7087,66 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) /* ERRORS */ no_protocols: { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_INVALID_PROTOCOL, + "Could not connect to server, no protocols left"); +#else /* no transport possible, post an error and stop */ GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), ("Could not connect to server, no protocols left")); +#endif return GST_RTSP_ERROR; } no_streams: { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_CONTENT_NOT_FOUND, + "SDP contains no streams"); +#else GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), ("SDP contains no streams")); +#endif return GST_RTSP_ERROR; } create_request_failed: { gchar *str = gst_rtsp_strresult (res); +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_BAD_REQUEST, + "Could not create request."); +#else GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), ("Could not create request. (%s)", str)); +#endif g_free (str); goto cleanup_error; } setup_transport_failed: { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_BAD_REQUEST, + "Could not setup transport."); +#else GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), ("Could not setup transport.")); +#endif res = GST_RTSP_ERROR; goto cleanup_error; } response_error: { +#ifndef TIZEN_FEATURE_RTSP_MODIFICATION const gchar *str = gst_rtsp_status_as_text (code); +#endif +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_UNEXPECTED_MSG, + "Error from Server ."); +#else GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Error (%d): %s", code, GST_STR_NULL (str))); +#endif res = GST_RTSP_ERROR; goto cleanup_error; } @@ -5835,8 +7155,13 @@ send_error: gchar *str = gst_rtsp_strresult (res); if (res != GST_RTSP_EINTR) { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_CONNECTION_FAIL, + "Could not send message."); +#else GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Could not send message. (%s)", str)); +#endif } else { GST_WARNING_OBJECT (src, "send interrupted"); } @@ -5845,8 +7170,13 @@ send_error: } no_transport: { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_BAD_TRANSPORT, + "Server did not select transport."); +#else GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), ("Server did not select transport.")); +#endif res = GST_RTSP_ERROR; goto cleanup_error; } @@ -5854,15 +7184,27 @@ nothing_to_activate: { /* none of the available error codes is really right .. */ if (unsupported_real) { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, + GST_RTSPSRC_ERROR_UNSUPPORTED_MEDIA_TYPE, + "No supported stream was found. You might need to install a GStreamer RTSP extension plugin for Real media streams."); +#else GST_ELEMENT_ERROR (src, STREAM, CODEC_NOT_FOUND, (_("No supported stream was found. You might need to install a " "GStreamer RTSP extension plugin for Real media streams.")), (NULL)); +#endif } else { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, + GST_RTSPSRC_ERROR_UNSUPPORTED_MEDIA_TYPE, + "No supported stream was found. You might need to allow more transport protocols or may otherwise be missing the right GStreamer RTSP extension plugin."); +#else GST_ELEMENT_ERROR (src, STREAM, CODEC_NOT_FOUND, (_("No supported stream was found. You might need to allow " "more transport protocols or may otherwise be missing " "the right GStreamer RTSP extension plugin.")), (NULL)); +#endif } return GST_RTSP_ERROR; } @@ -5911,10 +7253,21 @@ gst_rtspsrc_parse_range (GstRTSPSrc * src, const gchar * range, /* we need to start playback without clipping from the position reported by * the server */ segment->start = seconds; +#ifndef TIZEN_FEATURE_RTSP_MODIFICATION +/* +The range-min points to the start of the segment , not the current position. +After getting the current position from MSL during normal pause/resume or during seek , we should not +update the segment->position again with the rtp header npt timestamp. +*/ segment->position = seconds; +#endif if (therange->max.type == GST_RTSP_TIME_NOW) +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + seconds = 0; +#else seconds = -1; +#endif else if (therange->max.type == GST_RTSP_TIME_END) seconds = -1; else @@ -6020,8 +7373,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); @@ -6090,6 +7442,11 @@ gst_rtspsrc_open_from_sdp (GstRTSPSrc * src, GstSDPMessage * sdp, src->control = g_strdup (control); } +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + src->is_audio_codec_supported = FALSE; + src->is_video_codec_supported = FALSE; +#endif + /* create streams */ n_streams = gst_sdp_message_medias_len (sdp); for (i = 0; i < n_streams; i++) { @@ -6097,7 +7454,15 @@ gst_rtspsrc_open_from_sdp (GstRTSPSrc * src, GstSDPMessage * sdp, } src->state = GST_RTSP_STATE_INIT; - +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + /* Check for the support for the Media codecs */ + if ((!src->is_audio_codec_supported) && (!src->is_video_codec_supported)) { + GST_ERROR_OBJECT (src, "UnSupported Media Type !!!! \n"); + goto unsupported_file_type; + } else { + GST_DEBUG_OBJECT (src, "Supported Media Type. \n"); + } +#endif /* setup streams */ if ((res = gst_rtspsrc_setup_streams (src, async)) < 0) goto setup_failed; @@ -6117,6 +7482,17 @@ setup_failed: gst_rtspsrc_cleanup (src); return res; } +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION +unsupported_file_type: + { + gst_rtspsrc_post_error_message (src, + GST_RTSPSRC_ERROR_UNSUPPORTED_MEDIA_TYPE, + "No supported stream was found"); + res = GST_RTSP_ERROR; + gst_rtspsrc_cleanup (src); + return res; + } +#endif } static GstRTSPResult @@ -6146,7 +7522,7 @@ restart: /* create OPTIONS */ GST_DEBUG_OBJECT (src, "create options..."); res = - gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS, + gst_rtspsrc_init_request (src, &request, GST_RTSP_OPTIONS, src->conninfo.url_str); if (res < 0) goto create_request_failed; @@ -6169,7 +7545,7 @@ restart: /* create DESCRIBE */ GST_DEBUG_OBJECT (src, "create describe..."); res = - gst_rtsp_message_init_request (&request, GST_RTSP_DESCRIBE, + gst_rtspsrc_init_request (src, &request, GST_RTSP_DESCRIBE, src->conninfo.url_str); if (res < 0) goto create_request_failed; @@ -6203,7 +7579,7 @@ restart: } /* it could be that the DESCRIBE method was not implemented */ - if (!src->methods & GST_RTSP_DESCRIBE) + if (!(src->methods & GST_RTSP_DESCRIBE)) goto no_describe; /* check if reply is SDP */ @@ -6212,7 +7588,7 @@ 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) + if (g_ascii_strcasecmp (respcont, "application/sdp") != 0) goto wrong_content_type; } @@ -6234,8 +7610,13 @@ restart: /* ERRORS */ no_url: { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_INVALID_URL, + "No valid RTSP URL was provided"); +#else GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL), ("No valid RTSP URL was provided")); +#endif goto cleanup_error; } connect_failed: @@ -6243,8 +7624,13 @@ connect_failed: gchar *str = gst_rtsp_strresult (res); if (res != GST_RTSP_EINTR) { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_CONNECTION_FAIL, + "Failed to connect."); +#else GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ_WRITE, (NULL), ("Failed to connect. (%s)", str)); +#endif } else { GST_WARNING_OBJECT (src, "connect interrupted"); } @@ -6255,8 +7641,13 @@ create_request_failed: { gchar *str = gst_rtsp_strresult (res); +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_BAD_REQUEST, + "Could not create request."); +#else GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), ("Could not create request. (%s)", str)); +#endif g_free (str); goto cleanup_error; } @@ -6274,15 +7665,25 @@ methods_error: } wrong_content_type: { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_OPTION_NOT_SUPPORTED, + "Server does not support SDP. "); +#else GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), ("Server does not support SDP, got %s.", respcont)); +#endif res = GST_RTSP_ERROR; goto cleanup_error; } no_describe: { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_METHOD_NOT_ALLOWED, + "Server can not provide an SDP."); +#else GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), ("Server can not provide an SDP.")); +#endif res = GST_RTSP_ERROR; goto cleanup_error; } @@ -6385,7 +7786,7 @@ gst_rtspsrc_close (GstRTSPSrc * src, gboolean async, gboolean only_close) /* do TEARDOWN */ res = - gst_rtsp_message_init_request (&request, GST_RTSP_TEARDOWN, setup_url); + gst_rtspsrc_init_request (src, &request, GST_RTSP_TEARDOWN, setup_url); if (res < 0) goto create_request_failed; @@ -6431,8 +7832,13 @@ create_request_failed: { gchar *str = gst_rtsp_strresult (res); +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_BAD_REQUEST, + "Could not create request."); +#else GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), ("Could not create request. (%s)", str)); +#endif g_free (str); goto close; } @@ -6442,8 +7848,13 @@ send_error: gst_rtsp_message_unset (&request); if (res != GST_RTSP_EINTR) { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_CONNECTION_FAIL, + "Could not send message."); +#else GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Could not send message. (%s)", str)); +#endif } else { GST_WARNING_OBJECT (src, "TEARDOWN interrupted"); } @@ -6583,7 +7994,12 @@ static gchar * gen_range_header (GstRTSPSrc * src, GstSegment * segment) { gchar val_str[G_ASCII_DTOSTR_BUF_SIZE] = { 0, }; - +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + if (src->start_position != 0 && segment->position == 0) { + segment->position = src->start_position; + src->start_position = 0; + } +#endif if (src->range && src->range->min.type == GST_RTSP_TIME_NOW) { g_strlcpy (val_str, "now", sizeof (val_str)); } else { @@ -6594,19 +8010,30 @@ gen_range_header (GstRTSPSrc * src, GstSegment * segment) ((gdouble) segment->position) / GST_SECOND); } } +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + GST_DEBUG_OBJECT (src, "Range Header Added : npt=%s-", val_str); +#endif return g_strdup_printf ("npt=%s-", val_str); } static void clear_rtp_base (GstRTSPSrc * src, GstRTSPStream * stream) { + guint i, len; + stream->timebase = -1; stream->seqbase = -1; - if (stream->caps) { + + len = stream->ptmap->len; + for (i = 0; i < len; i++) { + PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); GstStructure *s; - stream->caps = gst_caps_make_writable (stream->caps); - s = gst_caps_get_structure (stream->caps, 0); + if (item->caps == NULL) + continue; + + item->caps = gst_caps_make_writable (item->caps); + s = gst_caps_get_structure (item->caps, 0); gst_structure_remove_fields (s, "clock-base", "seqnum-base", NULL); } } @@ -6668,8 +8095,6 @@ gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, gboolean async) if (src->manager) g_signal_emit_by_name (src->manager, "reset-sync", NULL); - gst_rtspsrc_set_state (src, GST_STATE_PLAYING); - /* construct a control url */ control = get_aggregate_control (src); @@ -6693,21 +8118,40 @@ gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, gboolean async) } /* do play */ - res = gst_rtsp_message_init_request (&request, GST_RTSP_PLAY, setup_url); + res = gst_rtspsrc_init_request (src, &request, GST_RTSP_PLAY, setup_url); if (res < 0) goto create_request_failed; if (src->need_range) { +#ifndef TIZEN_FEATURE_RTSP_MODIFICATION hval = gen_range_header (src, segment); gst_rtsp_message_take_header (&request, GST_RTSP_HDR_RANGE, hval); +#endif /* store the newsegment event so it can be sent from the streaming thread. */ - if (src->start_segment) - gst_event_unref (src->start_segment); - src->start_segment = gst_event_new_segment (&src->segment); + src->need_segment = TRUE; + } +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + else { +/* + Updating position with the MSL current position as gst_rtspsrc_get_position() does not return correct position. +*/ + GST_DEBUG_OBJECT (src, + " During normal pause-resume , segment->position=%" GST_TIME_FORMAT + ",src->start_position=%" GST_TIME_FORMAT, + GST_TIME_ARGS (segment->position), + GST_TIME_ARGS (src->start_position)); + segment->position = src->last_pos; } +/* + Sending the npt range request for each play request for updating the segment position properly. +*/ + hval = gen_range_header (src, segment); + gst_rtsp_message_take_header (&request, GST_RTSP_HDR_RANGE, hval); +#endif + if (segment->rate != 1.0) { gchar hval[G_ASCII_DTOSTR_BUF_SIZE]; @@ -6791,6 +8235,11 @@ gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, gboolean async) * the manager object when we set a new Range header (we did a seek) */ gst_rtspsrc_configure_caps (src, segment, src->need_range); + /* set to PLAYING after we have configured the caps, otherwise we + * might end up calling request_key (with SRTP) while caps are still + * being configured. */ + gst_rtspsrc_set_state (src, GST_STATE_PLAYING); + /* set again when needed */ src->need_range = FALSE; @@ -6831,8 +8280,13 @@ create_request_failed: { gchar *str = gst_rtsp_strresult (res); +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_BAD_REQUEST, + "Could not create request. "); +#else GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), ("Could not create request. (%s)", str)); +#endif g_free (str); goto done; } @@ -6842,8 +8296,13 @@ send_error: gst_rtsp_message_unset (&request); if (res != GST_RTSP_EINTR) { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_CONNECTION_FAIL, + "Could not send message."); +#else GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Could not send message. (%s)", str)); +#endif } else { GST_WARNING_OBJECT (src, "PLAY interrupted"); } @@ -6904,7 +8363,7 @@ gst_rtspsrc_pause (GstRTSPSrc * src, gboolean async) ("Sending PAUSE request")); if ((res = - gst_rtsp_message_init_request (&request, GST_RTSP_PAUSE, + gst_rtspsrc_init_request (src, &request, GST_RTSP_PAUSE, setup_url)) < 0) goto create_request_failed; @@ -6951,8 +8410,13 @@ create_request_failed: { gchar *str = gst_rtsp_strresult (res); +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_BAD_REQUEST, + "Could not create request."); +#else GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), ("Could not create request. (%s)", str)); +#endif g_free (str); goto done; } @@ -6962,8 +8426,13 @@ send_error: gst_rtsp_message_unset (&request); if (res != GST_RTSP_EINTR) { +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_CONNECTION_FAIL, + "Could not send message. "); +#else GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Could not send message. (%s)", str)); +#endif } else { GST_WARNING_OBJECT (src, "PAUSE interrupted"); } @@ -7065,7 +8534,7 @@ gst_rtspsrc_thread (GstRTSPSrc * src) src->pending_cmd = CMD_LOOP; else src->pending_cmd = CMD_WAIT; - GST_DEBUG_OBJECT (src, "got command %d", cmd); + GST_DEBUG_OBJECT (src, "got command %s", cmd_to_string (cmd)); /* we got the message command, so ensure communication is possible again */ gst_rtspsrc_connection_flush (src, FALSE); @@ -7130,6 +8599,7 @@ gst_rtspsrc_start (GstRTSPSrc * src) /* ERRORS */ task_error: { + GST_OBJECT_UNLOCK (src); GST_ERROR_OBJECT (src, "failed to create task"); return FALSE; } @@ -7177,8 +8647,12 @@ gst_rtspsrc_change_state (GstElement * element, GstStateChange transition) { GstRTSPSrc *rtspsrc; GstStateChangeReturn ret; +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + guint64 end_time; +#endif rtspsrc = GST_RTSPSRC (element); + GST_WARNING_OBJECT (rtspsrc, "State change transition: %d \n", transition); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: @@ -7219,6 +8693,18 @@ gst_rtspsrc_change_state (GstElement * element, GstStateChange transition) ret = GST_STATE_CHANGE_SUCCESS; break; case GST_STATE_CHANGE_READY_TO_PAUSED: +#ifdef TIZEN_FEATURE_RTSP_MODIFICATION + /* don't change to PAUSE state before complete stream opend. + see gst_rtspsrc_loop_complete_cmd() */ + g_mutex_lock (&(rtspsrc)->pause_lock); + end_time = g_get_monotonic_time () + 10 * G_TIME_SPAN_SECOND; + if (!g_cond_wait_until (&(rtspsrc)->open_end, &(rtspsrc)->pause_lock, + end_time)) { + GST_WARNING_OBJECT (rtspsrc, + "time out: stream opend is not completed yet.."); + } + g_mutex_unlock (&(rtspsrc)->pause_lock); +#endif ret = GST_STATE_CHANGE_NO_PREROLL; break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: @@ -7304,6 +8790,7 @@ gst_rtspsrc_uri_set_uri (GstURIHandler * handler, const gchar * uri, { GstRTSPSrc *src; GstRTSPResult res; + GstSDPResult sres; GstRTSPUrl *newurl = NULL; GstSDPMessage *sdp = NULL; @@ -7314,11 +8801,13 @@ gst_rtspsrc_uri_set_uri (GstURIHandler * handler, const gchar * uri, goto was_ok; if (g_str_has_prefix (uri, "rtsp-sdp://")) { - if ((res = gst_sdp_message_new (&sdp) < 0)) + sres = gst_sdp_message_new (&sdp); + if (sres < 0) goto sdp_failed; GST_DEBUG_OBJECT (src, "parsing SDP message"); - if ((res = gst_sdp_message_parse_uri (uri, sdp) < 0)) + sres = gst_sdp_message_parse_uri (uri, sdp); + if (sres < 0) goto invalid_sdp; } else { /* try to parse */ @@ -7359,14 +8848,14 @@ was_ok: } sdp_failed: { - GST_ERROR_OBJECT (src, "Could not create new SDP (%d)", res); + GST_ERROR_OBJECT (src, "Could not create new SDP (%d)", sres); g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, "Could not create SDP"); return FALSE; } invalid_sdp: { - GST_ERROR_OBJECT (src, "Not a valid SDP (%d) '%s'", res, + GST_ERROR_OBJECT (src, "Not a valid SDP (%d) '%s'", sres, GST_STR_NULL (uri)); gst_sdp_message_free (sdp); g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, @@ -7393,3 +8882,305 @@ 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; } + +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_INFO_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_INFO) + return; + + GST_INFO_OBJECT (src, "--------------------------------------------"); + switch (msg->type) { + case GST_RTSP_MESSAGE_REQUEST: + GST_INFO_OBJECT (src, "RTSP request message %p", msg); + GST_INFO_OBJECT (src, " request line:"); + GST_INFO_OBJECT (src, " method: '%s'", + gst_rtsp_method_as_text (msg->type_data.request.method)); + GST_INFO_OBJECT (src, " uri: '%s'", msg->type_data.request.uri); + GST_INFO_OBJECT (src, " version: '%s'", + gst_rtsp_version_as_text (msg->type_data.request.version)); + GST_INFO_OBJECT (src, " headers:"); + key_value_foreach (msg->hdr_fields, dump_key_value, src); + GST_INFO_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_INFO_OBJECT (src, " %s(%d)", body_string->str, size); + g_string_free (body_string, TRUE); + body_string = NULL; + } + break; + case GST_RTSP_MESSAGE_RESPONSE: + GST_INFO_OBJECT (src, "RTSP response message %p", msg); + GST_INFO_OBJECT (src, " status line:"); + GST_INFO_OBJECT (src, " code: '%d'", msg->type_data.response.code); + GST_INFO_OBJECT (src, " reason: '%s'", msg->type_data.response.reason); + GST_INFO_OBJECT (src, " version: '%s", + gst_rtsp_version_as_text (msg->type_data.response.version)); + GST_INFO_OBJECT (src, " headers:"); + key_value_foreach (msg->hdr_fields, dump_key_value, src); + gst_rtsp_message_get_body (msg, &data, &size); + GST_INFO_OBJECT (src, " body: length %d", size); + if (size > 0) { + body_string = g_string_new_len ((const gchar *) data, size); + GST_INFO_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_INFO_OBJECT (src, "HTTP request message %p", msg); + GST_INFO_OBJECT (src, " request line:"); + GST_INFO_OBJECT (src, " method: '%s'", + gst_rtsp_method_as_text (msg->type_data.request.method)); + GST_INFO_OBJECT (src, " uri: '%s'", msg->type_data.request.uri); + GST_INFO_OBJECT (src, " version: '%s'", + gst_rtsp_version_as_text (msg->type_data.request.version)); + GST_INFO_OBJECT (src, " headers:"); + key_value_foreach (msg->hdr_fields, dump_key_value, src); + GST_INFO_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_INFO_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_INFO_OBJECT (src, "HTTP response message %p", msg); + GST_INFO_OBJECT (src, " status line:"); + GST_INFO_OBJECT (src, " code: '%d'", msg->type_data.response.code); + GST_INFO_OBJECT (src, " reason: '%s'", msg->type_data.response.reason); + GST_INFO_OBJECT (src, " version: '%s'", + gst_rtsp_version_as_text (msg->type_data.response.version)); + GST_INFO_OBJECT (src, " headers:"); + key_value_foreach (msg->hdr_fields, dump_key_value, src); + gst_rtsp_message_get_body (msg, &data, &size); + GST_INFO_OBJECT (src, " body: length %d", size); + if (size > 0) { + body_string = g_string_new_len ((const gchar *) data, size); + GST_INFO_OBJECT (src, " %s(%d)", body_string->str, size); + g_string_free (body_string, TRUE); + body_string = NULL; + } + break; + case GST_RTSP_MESSAGE_DATA: + GST_INFO_OBJECT (src, "RTSP data message %p", msg); + GST_INFO_OBJECT (src, " channel: '%d'", msg->type_data.data.channel); + GST_INFO_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_INFO_OBJECT (src, " %s(%d)", body_string->str, size); + g_string_free (body_string, TRUE); + body_string = NULL; + } + break; + default: + GST_INFO_OBJECT (src, "unsupported message type %d", msg->type); + break; + } + GST_INFO_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, "--------------------------------------------"); +}