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
This commit is contained in:
Thibault Saunier 2017-04-22 09:26:07 -03:00
parent 8b38aa9c46
commit 9706199efb
3 changed files with 261 additions and 47 deletions

View file

@ -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);

View file

@ -125,7 +125,7 @@ struct _GstRTSPMediaPrivate
GstNetTimeProvider *nettime;
gboolean is_live;
gboolean seekable;
GstClockTimeDiff seekable;
gboolean buffering;
GstState target_state;
@ -661,6 +661,45 @@ default_create_rtpbin (GstRTSPMedia * media)
return rtpbin;
}
/* 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)
@ -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;
check_seekable (media);
for (i = 0; i < n; i++) {
GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
if (priv->seekable == 0) {
GST_FIXME_OBJECT (media, "Handle going back to 0 for none live"
" not seekable streams.");
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;
gst_query_parse_seeking (query, &format, &seekable, &start, &end);
priv->seekable = seekable;
}
gst_query_unref (query);
}
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;
}

View file

@ -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,