From: Thibault Saunier Date: Sat, 22 Apr 2017 12:26:07 +0000 (-0300) Subject: Start support for RTSP 2.0 X-Git-Tag: 1.19.3~495^2~394 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=9706199efb7d34b0dd7524567120def38e9e8f1d;p=platform%2Fupstream%2Fgstreamer.git Start support for RTSP 2.0 This adds basic support for new 2.0 features, though the protocol is subposdely backward incompatible, most semantics are the sames. This commit adds: - features: * version negotiation * pipelined requests support * Media-Properties support * Accept-Ranges support - APIs: * gst_rtsp_media_seekable The RTSP methods that have been removed when using 2.0 now return BAD_REQUEST. https://bugzilla.gnome.org/show_bug.cgi?id=781446 --- diff --git a/gst/rtsp-server/rtsp-client.c b/gst/rtsp-server/rtsp-client.c index f007bd5..2890407 100644 --- a/gst/rtsp-server/rtsp-client.c +++ b/gst/rtsp-server/rtsp-client.c @@ -93,6 +93,11 @@ struct _GstRTSPClientPrivate guint rtsp_ctrl_timeout_id; guint rtsp_ctrl_timeout_cnt; + + /* The version currently being used */ + GstRTSPVersion version; + + GHashTable *pipelined_requests; /* pipelined_request_id -> session_id */ }; static GMutex tunnels_lock; @@ -528,6 +533,8 @@ gst_rtsp_client_init (GstRTSPClient * client) priv->transports = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); + priv->pipelined_requests = g_hash_table_new_full (g_str_hash, + g_str_equal, g_free, g_free); } static GstRTSPFilterResult @@ -800,6 +807,10 @@ send_message (GstRTSPClient * client, GstRTSPContext * ctx, if (close) gst_rtsp_message_add_header (message, GST_RTSP_HDR_CONNECTION, "close"); + if (ctx->request) + message->type_data.response.version = + ctx->request->type_data.request.version; + g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_SEND_MESSAGE], 0, ctx, message); @@ -1251,6 +1262,12 @@ handle_get_param_request (GstRTSPClient * client, GstRTSPContext * ctx) goto bad_request; if (size == 0 || !data || strlen ((char *) data) == 0) { + if (ctx->request->type_data.request.version >= GST_RTSP_VERSION_2_0) { + GST_ERROR_OBJECT (client, "Using PLAY request for keep-alive is forbidden" + " in RTSP 2.0"); + goto bad_request; + } + /* no body (or only '\0'), keep-alive request */ send_generic_response (client, GST_RTSP_STS_OK, ctx); } else { @@ -1498,6 +1515,7 @@ handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx) GstRTSPRangeUnit unit = GST_RTSP_RANGE_NPT; gchar *path, *rtpinfo; gint matched; + gchar *seek_style = NULL; GstRTSPStatusCode sig_result; if (!(session = ctx->session)) @@ -1546,10 +1564,26 @@ handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx) if (res == GST_RTSP_OK) { if (gst_rtsp_range_parse (str, &range) == GST_RTSP_OK) { GstRTSPMediaStatus media_status; + GstSeekFlags flags = 0; + + if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_SEEK_STYLE, + &seek_style, 0)) { + if (g_strcmp0 (seek_style, "RAP") == 0) + flags = GST_SEEK_FLAG_ACCURATE; + else if (g_strcmp0 (seek_style, "CoRAP") == 0) + flags = GST_SEEK_FLAG_KEY_UNIT; + else if (g_strcmp0 (seek_style, "First-Prior") == 0) + flags = GST_SEEK_FLAG_KEY_UNIT & GST_SEEK_FLAG_SNAP_BEFORE; + else if (g_strcmp0 (seek_style, "Next") == 0) + flags = GST_SEEK_FLAG_KEY_UNIT & GST_SEEK_FLAG_SNAP_AFTER; + else + GST_FIXME_OBJECT (client, "Add support for seek style %s", + seek_style); + } /* we have a range, seek to the position */ unit = range->unit; - gst_rtsp_media_seek (media, range); + gst_rtsp_media_seek_full (media, range, flags); gst_rtsp_range_free (range); media_status = gst_rtsp_media_get_status (media); @@ -1570,6 +1604,9 @@ handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx) if (rtpinfo) gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_RTP_INFO, rtpinfo); + if (seek_style) + gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_SEEK_STYLE, + seek_style); /* add the range */ str = gst_rtsp_media_get_range_string (media, TRUE, unit); @@ -2208,6 +2245,7 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx) gint matched; gboolean new_session = FALSE; GstRTSPStatusCode sig_result; + gchar *pipelined_request_id = NULL, *accept_range = NULL; if (!ctx->uri) goto no_uri; @@ -2223,6 +2261,11 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx) if (res != GST_RTSP_OK) goto no_transport; + /* Handle Pipelined-requests if using >= 2.0 */ + if (ctx->request->type_data.request.version >= GST_RTSP_VERSION_2_0) + gst_rtsp_message_get_header (ctx->request, + GST_RTSP_HDR_PIPELINED_REQUESTS, &pipelined_request_id, 0); + /* we create the session after parsing stuff so that we don't make * a session for malformed requests */ if (priv->session_pool == NULL) @@ -2292,6 +2335,9 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx) if (!(session = gst_rtsp_session_pool_create (priv->session_pool))) goto service_unavailable; + /* Pipelined requests should be cleared between sessions */ + g_hash_table_remove_all (priv->pipelined_requests); + /* make sure this client is closed when the session is closed */ client_watch_session (client, session); @@ -2303,6 +2349,11 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx) ctx->session = session; } + if (pipelined_request_id) { + g_hash_table_insert (client->priv->pipelined_requests, + g_strdup (pipelined_request_id), + g_strdup (gst_rtsp_session_get_sessionid (session))); + } rtsp_ctrl_timeout_remove (priv); if (!klass->configure_client_media (client, media, stream, ctx)) @@ -2328,6 +2379,33 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx) goto keymgmt_error; } + if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_ACCEPT_RANGES, + &accept_range, 0) == GST_RTSP_OK) { + GEnumValue *runit = NULL; + gint i; + gchar **valid_ranges; + GEnumClass *runit_class = g_type_class_ref (GST_TYPE_RTSP_RANGE_UNIT); + + gst_rtsp_message_dump (ctx->request); + valid_ranges = g_strsplit (accept_range, ",", -1); + + for (i = 0; valid_ranges[i]; i++) { + gchar *range = valid_ranges[i]; + + while (*range == ' ') + range++; + + runit = g_enum_get_value_by_nick (runit_class, range); + if (runit) + break; + } + g_strfreev (valid_ranges); + g_type_class_unref (runit_class); + + if (!runit) + goto unsupported_range_unit; + } + if (sessmedia == NULL) { /* manage the media in our session now, if not done already */ sessmedia = @@ -2386,6 +2464,33 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx) trans_str); g_free (trans_str); + if (pipelined_request_id) + gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_PIPELINED_REQUESTS, + pipelined_request_id); + + if (ctx->request->type_data.request.version >= GST_RTSP_VERSION_2_0) { + GstClockTimeDiff seekable = gst_rtsp_media_seekable (media); + GString *media_properties = g_string_new (NULL); + + if (seekable == -1) + g_string_append (media_properties, + "No-Seeking,Time-Progressing,Time-Duration=0.0"); + else if (seekable == 0) + g_string_append (media_properties, "Beginning-Only"); + else if (seekable == G_MAXINT64) + g_string_append (media_properties, "Random-Access"); + else + g_string_append_printf (media_properties, + "Random-Access=%f, Unlimited, Immutable", + (gdouble) seekable / GST_SECOND); + + gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_MEDIA_PROPERTIES, + g_string_free (media_properties, FALSE)); + /* TODO Check how Accept-Ranges should be filled */ + gst_rtsp_message_add_header (ctx->request, GST_RTSP_HDR_ACCEPT_RANGES, + "npt, clock, smpte, clock"); + } + send_message (client, ctx, ctx->response, FALSE); /* update the state */ @@ -2505,6 +2610,13 @@ unsupported_mode: send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, ctx); goto cleanup_transport; } +unsupported_range_unit: + { + GST_ERROR ("Client %p: does not support any range format we support", + client); + send_generic_response (client, GST_RTSP_STS_NOT_IMPLEMENTED, ctx); + goto cleanup_transport; + } keymgmt_error: { GST_ERROR ("client %p: keymgmt error", client); @@ -3036,7 +3148,8 @@ unsuspend_failed: } static gboolean -handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx) +handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx, + GstRTSPVersion version) { GstRTSPMethod options; gchar *str; @@ -3046,10 +3159,14 @@ handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx) GST_RTSP_OPTIONS | GST_RTSP_PAUSE | GST_RTSP_PLAY | - GST_RTSP_RECORD | GST_RTSP_ANNOUNCE | GST_RTSP_SETUP | GST_RTSP_GET_PARAMETER | GST_RTSP_SET_PARAMETER | GST_RTSP_TEARDOWN; + if (version < GST_RTSP_VERSION_2_0) { + options |= GST_RTSP_RECORD; + options |= GST_RTSP_ANNOUNCE; + } + str = gst_rtsp_options_as_text (options); gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK, @@ -3209,7 +3326,7 @@ handle_request (GstRTSPClient * client, GstRTSPMessage * request) GstRTSPContext sctx = { NULL }, *ctx; GstRTSPMessage response = { 0 }; gchar *unsupported_reqs = NULL; - gchar *sessid; + gchar *sessid = NULL, *pipelined_request_id = NULL; if (!(ctx = gst_rtsp_context_get_current ())) { ctx = &sctx; @@ -3233,7 +3350,7 @@ handle_request (GstRTSPClient * client, GstRTSPMessage * request) gst_rtsp_version_as_text (version)); /* we can only handle 1.0 requests */ - if (version != GST_RTSP_VERSION_1_0) + if (version != GST_RTSP_VERSION_1_0 && version != GST_RTSP_VERSION_2_0) goto not_supported; ctx->method = method; @@ -3272,7 +3389,20 @@ handle_request (GstRTSPClient * client, GstRTSPMessage * request) } /* get the session if there is any */ - res = gst_rtsp_message_get_header (request, GST_RTSP_HDR_SESSION, &sessid, 0); + res = gst_rtsp_message_get_header (request, GST_RTSP_HDR_PIPELINED_REQUESTS, + &pipelined_request_id, 0); + if (res == GST_RTSP_OK) { + sessid = g_hash_table_lookup (client->priv->pipelined_requests, + pipelined_request_id); + + if (!sessid) + res = GST_RTSP_ERROR; + } + + if (res != GST_RTSP_OK) + res = + gst_rtsp_message_get_header (request, GST_RTSP_HDR_SESSION, &sessid, 0); + if (res == GST_RTSP_OK) { if (priv->session_pool == NULL) goto no_pool; @@ -3334,7 +3464,8 @@ handle_request (GstRTSPClient * client, GstRTSPMessage * request) /* now see what is asked and dispatch to a dedicated handler */ switch (method) { case GST_RTSP_OPTIONS: - handle_options_request (client, ctx); + priv->version = version; + handle_options_request (client, ctx, version); break; case GST_RTSP_DESCRIBE: handle_describe_request (client, ctx); @@ -3358,9 +3489,13 @@ handle_request (GstRTSPClient * client, GstRTSPMessage * request) handle_get_param_request (client, ctx); break; case GST_RTSP_ANNOUNCE: + if (version >= GST_RTSP_VERSION_2_0) + goto invalid_command_for_version; handle_announce_request (client, ctx); break; case GST_RTSP_RECORD: + if (version >= GST_RTSP_VERSION_2_0) + goto invalid_command_for_version; handle_record_request (client, ctx); break; case GST_RTSP_REDIRECT: @@ -3394,6 +3529,15 @@ not_supported: ctx); goto done; } +invalid_command_for_version: + { + if (priv->watch != NULL) + gst_rtsp_watch_set_send_backlog (priv->watch, 0, WATCH_BACKLOG_SIZE); + + GST_ERROR ("client %p: invalid command for version", client); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + goto done; + } bad_request: { GST_ERROR ("client %p: bad request", client); diff --git a/gst/rtsp-server/rtsp-media.c b/gst/rtsp-server/rtsp-media.c index 996f453..e4df4ea 100644 --- a/gst/rtsp-server/rtsp-media.c +++ b/gst/rtsp-server/rtsp-media.c @@ -125,7 +125,7 @@ struct _GstRTSPMediaPrivate GstNetTimeProvider *nettime; gboolean is_live; - gboolean seekable; + GstClockTimeDiff seekable; gboolean buffering; GstState target_state; @@ -663,6 +663,45 @@ default_create_rtpbin (GstRTSPMedia * media) /* must be called with state lock */ static void +check_seekable (GstRTSPMedia * media) +{ + GstQuery *query; + GstRTSPMediaPrivate *priv = media->priv; + + /* Update the seekable state of the pipeline in case it changed */ + if ((priv->transport_mode & GST_RTSP_TRANSPORT_MODE_RECORD)) { + /* TODO: Seeking for RECORD? */ + priv->seekable = -1; + } else { + guint i, n = priv->streams->len; + + for (i = 0; i < n; i++) { + GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); + + if (gst_rtsp_stream_get_publish_clock_mode (stream) == + GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET) { + priv->seekable = -1; + return; + } + } + } + + query = gst_query_new_seeking (GST_FORMAT_TIME); + if (gst_element_query (priv->pipeline, query)) { + GstFormat format; + gboolean seekable; + gint64 start, end; + + gst_query_parse_seeking (query, &format, &seekable, &start, &end); + priv->seekable = seekable ? G_MAXINT64 : 0.0; + } + + gst_query_unref (query); +} + + +/* must be called with state lock */ +static void collect_media_stats (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; @@ -730,6 +769,8 @@ collect_media_stats (GstRTSPMedia * media) priv->range.max.seconds = ((gdouble) stop) / GST_SECOND; priv->range_stop = stop; } + + check_seekable (media); } } @@ -2114,9 +2155,10 @@ gst_rtsp_media_get_status (GstRTSPMedia * media) } /** - * gst_rtsp_media_seek: + * gst_rtsp_media_seek_full: * @media: a #GstRTSPMedia * @range: (transfer none): a #GstRTSPTimeRange + * @flags: The minimal set of #GstSeekFlags to use * * Seek the pipeline of @media to @range. @media must be prepared with * gst_rtsp_media_prepare(). @@ -2124,14 +2166,14 @@ gst_rtsp_media_get_status (GstRTSPMedia * media) * Returns: %TRUE on success. */ gboolean -gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) +gst_rtsp_media_seek_full (GstRTSPMedia * media, GstRTSPTimeRange * range, + GstSeekFlags flags) { GstRTSPMediaClass *klass; GstRTSPMediaPrivate *priv; gboolean res; GstClockTime start, stop; GstSeekType start_type, stop_type; - GstQuery *query; gint64 current_position; klass = GST_RTSP_MEDIA_GET_CLASS (media); @@ -2147,37 +2189,16 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) goto not_prepared; /* Update the seekable state of the pipeline in case it changed */ - if ((priv->transport_mode & GST_RTSP_TRANSPORT_MODE_RECORD)) { - /* TODO: Seeking for RECORD? */ - priv->seekable = FALSE; - } else { - guint i, n = priv->streams->len; - - for (i = 0; i < n; i++) { - GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); - - if (gst_rtsp_stream_get_publish_clock_mode (stream) == - GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET) { - priv->seekable = FALSE; - goto not_seekable; - } - } - - query = gst_query_new_seeking (GST_FORMAT_TIME); - if (gst_element_query (priv->pipeline, query)) { - GstFormat format; - gboolean seekable; - gint64 start, end; + check_seekable (media); - gst_query_parse_seeking (query, &format, &seekable, &start, &end); - priv->seekable = seekable; - } - - gst_query_unref (query); - } + if (priv->seekable == 0) { + GST_FIXME_OBJECT (media, "Handle going back to 0 for none live" + " not seekable streams."); - if (!priv->seekable) goto not_seekable; + } else if (priv->seekable < 0) { + goto not_seekable; + } start_type = stop_type = GST_SEEK_TYPE_NONE; @@ -2205,14 +2226,18 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) stop_type = GST_SEEK_TYPE_SET; if (start != GST_CLOCK_TIME_NONE || stop != GST_CLOCK_TIME_NONE) { - GstSeekFlags flags; + gboolean had_flags = flags != 0; GST_INFO ("seeking to %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); /* depends on the current playing state of the pipeline. We might need to * queue this until we get EOS. */ - flags = GST_SEEK_FLAG_FLUSH; + if (had_flags) + flags |= GST_SEEK_FLAG_FLUSH; + else + flags = GST_SEEK_FLAG_FLUSH; + /* if range start was not supplied we must continue from current position. * but since we're doing a flushing seek, let us query the current position @@ -2225,12 +2250,14 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) GST_TIME_ARGS (current_position)); start = current_position; start_type = GST_SEEK_TYPE_SET; - flags |= GST_SEEK_FLAG_ACCURATE; + if (!had_flags) + flags |= GST_SEEK_FLAG_ACCURATE; } } else { /* only set keyframe flag when modifying start */ if (start_type != GST_SEEK_TYPE_NONE) - flags |= GST_SEEK_FLAG_KEY_UNIT; + if (!had_flags) + flags |= GST_SEEK_FLAG_KEY_UNIT; } if (start == current_position && stop_type == GST_SEEK_TYPE_NONE) { @@ -2300,6 +2327,24 @@ preroll_failed: } } + +/** + * gst_rtsp_media_seek: + * @media: a #GstRTSPMedia + * @range: (transfer none): a #GstRTSPTimeRange + * + * Seek the pipeline of @media to @range. @media must be prepared with + * gst_rtsp_media_prepare(). + * + * Returns: %TRUE on success. + */ +gboolean +gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) +{ + return gst_rtsp_media_seek_full (media, range, 0); +} + + static void stream_collect_blocking (GstRTSPStream * stream, gboolean * blocked) { @@ -2675,18 +2720,16 @@ start_preroll (GstRTSPMedia * media) switch (ret) { case GST_STATE_CHANGE_SUCCESS: GST_INFO ("SUCCESS state change for media %p", media); - priv->seekable = TRUE; break; case GST_STATE_CHANGE_ASYNC: GST_INFO ("ASYNC state change for media %p", media); - priv->seekable = TRUE; break; case GST_STATE_CHANGE_NO_PREROLL: /* we need to go to PLAYING */ GST_INFO ("NO_PREROLL state change: live media %p", media); /* FIXME we disable seeking for live streams for now. We should perform a * seeking query in preroll instead */ - priv->seekable = FALSE; + priv->seekable = -1; priv->is_live = TRUE; if (!(priv->transport_mode & GST_RTSP_TRANSPORT_MODE_RECORD)) { /* start blocked to make sure nothing goes to the sink */ @@ -2953,7 +2996,7 @@ gst_rtsp_media_prepare (GstRTSPMedia * media, GstRTSPThread * thread) /* reset some variables */ priv->is_live = FALSE; - priv->seekable = FALSE; + priv->seekable = -1; priv->buffering = FALSE; /* we're preparing now */ @@ -3937,3 +3980,24 @@ gst_rtsp_media_get_transport_mode (GstRTSPMedia * media) return res; } + +/** + * gst_rtsp_media_get_seekbale: + * @media: a #GstRTSPMedia + * + * Check if the pipeline for @media seek and up to what point in time, + * it can seek. + * + * Returns: -1 if the stream is not seekable, 0 if seekable only to the beginning + * and > 0 to indicate the longest duration between any two random access points. + * G_MAXINT64 means any value is possible. + */ +GstClockTimeDiff +gst_rtsp_media_seekable (GstRTSPMedia * media) +{ + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + + /* Currently we are not able to seek on live streams, + * and no stream is seekable only to the beginning */ + return media->priv->seekable; +} diff --git a/gst/rtsp-server/rtsp-media.h b/gst/rtsp-server/rtsp-media.h index 9f95e5b..9c63d8f 100644 --- a/gst/rtsp-server/rtsp-media.h +++ b/gst/rtsp-server/rtsp-media.h @@ -362,6 +362,12 @@ GstRTSPStream * gst_rtsp_media_find_stream (GstRTSPMedia *media, cons GST_EXPORT gboolean gst_rtsp_media_seek (GstRTSPMedia *media, GstRTSPTimeRange *range); +gboolean gst_rtsp_media_seek_full (GstRTSPMedia *media, + GstRTSPTimeRange *range, + GstSeekFlags flags); + +GST_EXPORT +GstClockTimeDiff gst_rtsp_media_seekable (GstRTSPMedia *media); GST_EXPORT gchar * gst_rtsp_media_get_range_string (GstRTSPMedia *media,