diff options
author | Branko Subasic <branko@subasic.net> | 2018-10-12 08:53:04 +0200 |
---|---|---|
committer | Mathieu Duponchelle <mathieu@centricular.com> | 2019-06-04 14:32:51 +0200 |
commit | bc745896012a72f327ad1d135d695336b88bd1d3 (patch) | |
tree | 9ddbf7f0e222f564a04a15f7c9c89a0ce0546884 | |
parent | 65d9aa327cd1844934836249cd4463edf09c725d (diff) |
rtsp-client: add support for Scale and Speed header
Add support for the RTSP Scale and Speed headers by setting the rate in
the seek to (scale*speed). We then check the resulting segment for rate
and applied rate, and use them as values for the Speed and Scale headers
respectively.
https://bugzilla.gnome.org/show_bug.cgi?id=754575
-rw-r--r-- | gst/rtsp-server/rtsp-client.c | 148 | ||||
-rw-r--r-- | gst/rtsp-server/rtsp-media.c | 55 | ||||
-rw-r--r-- | gst/rtsp-server/rtsp-media.h | 3 | ||||
-rw-r--r-- | tests/check/gst/client.c | 211 |
4 files changed, 385 insertions, 32 deletions
diff --git a/gst/rtsp-server/rtsp-client.c b/gst/rtsp-server/rtsp-client.c index 6f58b37c07..d3c484b70b 100644 --- a/gst/rtsp-server/rtsp-client.c +++ b/gst/rtsp-server/rtsp-client.c @@ -1730,9 +1730,97 @@ make_base_url (GstRTSPClient * client, GstRTSPUrl * url, const gchar * path) return result; } +/* Check if the given header of type double is present and, if so, + * put it's value in the supplied variable. + */ +static GstRTSPStatusCode +parse_header_value_double (GstRTSPClient * client, GstRTSPContext * ctx, + GstRTSPHeaderField header, gboolean * present, gdouble * value) +{ + GstRTSPResult res; + gchar *str; + gchar *end; + + res = gst_rtsp_message_get_header (ctx->request, header, &str, 0); + if (res == GST_RTSP_OK) { + *value = g_ascii_strtod (str, &end); + if (end == str) + goto parse_header_failed; + + GST_DEBUG ("client %p: got '%s', value %f", client, + gst_rtsp_header_as_text (header), *value); + *present = TRUE; + } else { + *present = FALSE; + } + + return GST_RTSP_STS_OK; + +parse_header_failed: + { + GST_ERROR ("client %p: failed parsing '%s' header", client, + gst_rtsp_header_as_text (header)); + return GST_RTSP_STS_BAD_REQUEST; + } +} + +/* Parse scale and speed headers, if present, and set the rate to + * (rate * scale * speed) */ +static GstRTSPStatusCode +parse_scale_and_speed (GstRTSPClient * client, GstRTSPContext * ctx, + gboolean * scale_present, gboolean * speed_present, gdouble * rate) +{ + gdouble scale = 1.0; + gdouble speed = 1.0; + GstRTSPStatusCode status; + + GST_DEBUG ("got rate %f", *rate); + + status = parse_header_value_double (client, ctx, GST_RTSP_HDR_SCALE, + scale_present, &scale); + if (status != GST_RTSP_STS_OK) + return status; + + if (scale_present) { + GST_DEBUG ("got Scale %f", scale); + if (scale == 0) + goto bad_scale_value; + *rate *= scale; + } + + GST_DEBUG ("rate after parsing Scale %f", *rate); + + status = parse_header_value_double (client, ctx, GST_RTSP_HDR_SPEED, + speed_present, &speed); + if (status != GST_RTSP_STS_OK) + return status; + + if (speed_present) { + GST_DEBUG ("got Speed %f", speed); + if (speed <= 0) + goto bad_speed_value; + *rate *= speed; + } + + GST_DEBUG ("rate after parsing Speed %f", *rate); + + return status; + +bad_scale_value: + { + GST_ERROR ("client %p: bad 'Scale' header value (%f)", client, scale); + return GST_RTSP_STS_BAD_REQUEST; + } +bad_speed_value: + { + GST_ERROR ("client %p: bad 'Speed' header value (%f)", client, speed); + return GST_RTSP_STS_BAD_REQUEST; + } +} + static GstRTSPStatusCode setup_play_mode (GstRTSPClient * client, GstRTSPContext * ctx, - GstRTSPRangeUnit * unit) + GstRTSPRangeUnit * unit, gboolean * scale_present, gboolean * speed_present) { gchar *str; GstRTSPResult res; @@ -1740,7 +1828,7 @@ setup_play_mode (GstRTSPClient * client, GstRTSPContext * ctx, gdouble rate = 1.0; GstSeekFlags flags = GST_SEEK_FLAG_NONE; GstRTSPClientClass *klass = GST_RTSP_CLIENT_GET_CLASS (client); - GstRTSPStatusCode status; + GstRTSPStatusCode rtsp_status_code; /* parse the range header if we have one */ res = gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_RANGE, &str, 0); @@ -1771,10 +1859,21 @@ setup_play_mode (GstRTSPClient * client, GstRTSPContext * ctx, } } + /* check for scale and/or speed headers + * we will set the seek rate to (speed * scale) and let the media decide + * the resulting scale and speed. in the response we will use rate and applied + * rate from the resulting segment as values for the speed and scale headers + * respectively */ + rtsp_status_code = parse_scale_and_speed (client, ctx, scale_present, + speed_present, &rate); + if (rtsp_status_code != GST_RTSP_STS_OK) + goto scale_speed_failed; + /* give the application a chance to tweak range, flags, or rate */ if (klass->adjust_play_mode != NULL) { - status = klass->adjust_play_mode (client, ctx, &range, &flags, &rate); - if (status != GST_RTSP_STS_OK) + rtsp_status_code = + klass->adjust_play_mode (client, ctx, &range, &flags, &rate); + if (rtsp_status_code != GST_RTSP_STS_OK) goto adjust_play_mode_failed; } @@ -1793,12 +1892,20 @@ parse_range_failed: GST_ERROR ("client %p: failed parsing range header", client); return GST_RTSP_STS_BAD_REQUEST; } +scale_speed_failed: + { + if (range != NULL) + gst_rtsp_range_free (range); + GST_ERROR ("client %p: failed parsing Scale or Speed headers", client); + return rtsp_status_code; + } adjust_play_mode_failed: { - GST_ERROR ("client %p: sub class returned bad code (%d)", client, status); + GST_ERROR ("client %p: sub class returned bad code (%d)", client, + rtsp_status_code); if (range != NULL) gst_rtsp_range_free (range); - return status; + return rtsp_status_code; } seek_failed: { @@ -1824,6 +1931,10 @@ handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx) gchar *seek_style = NULL; GstRTSPStatusCode sig_result; GPtrArray *transports; + gboolean scale_present; + gboolean speed_present; + gdouble rate; + gdouble applied_rate; if (!(session = ctx->session)) goto no_session; @@ -1874,7 +1985,7 @@ handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx) if (!gst_rtsp_media_unsuspend (media)) goto unsuspend_failed; - code = setup_play_mode (client, ctx, &unit); + code = setup_play_mode (client, ctx, &unit, &scale_present, &speed_present); if (code != GST_RTSP_STS_OK) goto invalid_mode; @@ -1899,6 +2010,23 @@ handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx) if (str) gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_RANGE, str); + if (!gst_rtsp_media_is_receive_only (media)) { + /* the scale and speed headers must always be added if they were present in + * the request. however, even if they were not, we still add them if + * applied_rate or rate deviate from the "normal", i.e. 1.0 */ + if (!gst_rtsp_media_get_rates (media, &rate, &applied_rate)) + goto get_rates_error; + g_assert (rate != 0 && applied_rate != 0); + + if (scale_present || applied_rate != 1.0) + gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_SCALE, + g_strdup_printf ("%1.3f", applied_rate)); + + if (speed_present || rate != 1.0) + gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_SPEED, + g_strdup_printf ("%1.3f", rate)); + } + send_message (client, ctx, ctx->response, FALSE); /* start playing after sending the response */ @@ -1976,6 +2104,12 @@ unsupported_mode: send_generic_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx); return FALSE; } +get_rates_error: + { + GST_ERROR ("client %p: failed obtaining rate and applied_rate", client); + send_generic_response (client, GST_RTSP_STS_INTERNAL_SERVER_ERROR, ctx); + return FALSE; + } } static void diff --git a/gst/rtsp-server/rtsp-media.c b/gst/rtsp-server/rtsp-media.c index e03359e66d..6946211ec4 100644 --- a/gst/rtsp-server/rtsp-media.c +++ b/gst/rtsp-server/rtsp-media.c @@ -732,25 +732,6 @@ default_create_rtpbin (GstRTSPMedia * media) return rtpbin; } -static gboolean -is_receive_only (GstRTSPMedia * media) -{ - GstRTSPMediaPrivate *priv = media->priv; - gboolean recive_only = TRUE; - guint i; - - for (i = 0; i < priv->streams->len; i++) { - GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); - if (gst_rtsp_stream_is_sender (stream) || - !gst_rtsp_stream_is_receiver (stream)) { - recive_only = FALSE; - break; - } - } - - return recive_only; -} - /* must be called with state lock */ static void check_seekable (GstRTSPMedia * media) @@ -759,7 +740,7 @@ check_seekable (GstRTSPMedia * media) GstRTSPMediaPrivate *priv = media->priv; /* Update the seekable state of the pipeline in case it changed */ - if (is_receive_only (media)) { + if (gst_rtsp_media_is_receive_only (media)) { /* TODO: Seeking for "receive-only"? */ priv->seekable = -1; } else { @@ -2984,8 +2965,9 @@ default_handle_message (GstRTSPMedia * media, GstMessage * message) GST_DEBUG ("%p: went from %s to %s (pending %s)", media, gst_element_state_get_name (old), gst_element_state_get_name (new), gst_element_state_get_name (pending)); - if (priv->no_more_pads_pending == 0 && is_receive_only (media) && - old == GST_STATE_READY && new == GST_STATE_PAUSED) { + if (priv->no_more_pads_pending == 0 + && gst_rtsp_media_is_receive_only (media) && old == GST_STATE_READY + && new == GST_STATE_PAUSED) { GST_INFO ("%p: went to PAUSED, prepared now", media); collect_media_stats (media); @@ -3459,7 +3441,7 @@ start_prepare (GstRTSPMedia * media) g_object_set_data (G_OBJECT (elem), "gst-rtsp-dynpay-handlers", handlers); } - if (priv->nb_dynamic_elements == 0 && is_receive_only (media)) { + if (priv->nb_dynamic_elements == 0 && gst_rtsp_media_is_receive_only (media)) { /* If we are receive_only (RECORD), do not try to preroll, to avoid * a second ASYNC state change failing */ priv->is_live = TRUE; @@ -4286,7 +4268,7 @@ default_unsuspend (GstRTSPMedia * media) switch (priv->suspend_mode) { case GST_RTSP_SUSPEND_MODE_NONE: - if (is_receive_only (media)) + if (gst_rtsp_media_is_receive_only (media)) break; if (media_streams_blocking (media)) { gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); @@ -4713,3 +4695,28 @@ gst_rtsp_media_complete_pipeline (GstRTSPMedia * media, GPtrArray * transports) return TRUE; } + +/** + * gst_rtsp_media_is_receive_only: + * + * Returns: %TRUE if @media is receive-only, %FALSE otherwise. + * Since: 1.18 + */ +gboolean +gst_rtsp_media_is_receive_only (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + gboolean receive_only = TRUE; + guint i; + + for (i = 0; i < priv->streams->len; i++) { + GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); + if (gst_rtsp_stream_is_sender (stream) || + !gst_rtsp_stream_is_receiver (stream)) { + receive_only = FALSE; + break; + } + } + + return receive_only; +} diff --git a/gst/rtsp-server/rtsp-media.h b/gst/rtsp-server/rtsp-media.h index 0af6574682..58d5aa40af 100644 --- a/gst/rtsp-server/rtsp-media.h +++ b/gst/rtsp-server/rtsp-media.h @@ -417,6 +417,9 @@ void gst_rtsp_media_set_pipeline_state (GstRTSPMedia * media, GST_RTSP_SERVER_API gboolean gst_rtsp_media_complete_pipeline (GstRTSPMedia * media, GPtrArray * transports); +GST_RTSP_SERVER_API +gboolean gst_rtsp_media_is_receive_only (GstRTSPMedia * media); + #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPMedia, gst_object_unref) #endif diff --git a/tests/check/gst/client.c b/tests/check/gst/client.c index cbe846efcf..5f10c4372a 100644 --- a/tests/check/gst/client.c +++ b/tests/check/gst/client.c @@ -32,6 +32,10 @@ static gchar *session_id; static gint cseq; static guint expected_session_timeout = 60; static const gchar *expected_unsupported_header; +static const gchar *expected_scale_header; +static const gchar *expected_speed_header; +static gdouble fake_rate_value = 0; +static gdouble fake_applied_rate_value = 0; static gboolean test_response_200 (GstRTSPClient * client, GstRTSPMessage * response, @@ -1707,7 +1711,211 @@ GST_START_TEST (test_client_multicast_invalid_ttl) GST_END_TEST; -static Suite * +static gboolean +test_response_scale_speed (GstRTSPClient * client, GstRTSPMessage * response, + gboolean close, gpointer user_data) +{ + GstRTSPStatusCode code; + const gchar *reason; + GstRTSPVersion version; + gchar *header_value; + + fail_unless (gst_rtsp_message_get_type (response) == + GST_RTSP_MESSAGE_RESPONSE); + + fail_unless (gst_rtsp_message_parse_response (response, &code, &reason, + &version) + == GST_RTSP_OK); + fail_unless (code == GST_RTSP_STS_OK); + fail_unless (g_str_equal (reason, "OK")); + fail_unless (version == GST_RTSP_VERSION_1_0); + + fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_RANGE, + &header_value, 0) == GST_RTSP_OK); + + if (expected_scale_header != NULL) { + fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_SCALE, + &header_value, 0) == GST_RTSP_OK); + ck_assert_str_eq (header_value, expected_scale_header); + } else { + fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_SCALE, + &header_value, 0) == GST_RTSP_ENOTIMPL); + } + + if (expected_speed_header != NULL) { + fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_SPEED, + &header_value, 0) == GST_RTSP_OK); + ck_assert_str_eq (header_value, expected_speed_header); + } else { + fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_SPEED, + &header_value, 0) == GST_RTSP_ENOTIMPL); + } + + return TRUE; +} + +/* Probe that tweaks segment events according to the values of the + * fake_rate_value and fake_applied_rate_value variables. Used to simulate + * seek results with different combinations of rate and applied rate. + */ +static GstPadProbeReturn +rate_tweaking_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) +{ + GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + GstSegment segment; + + if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) { + GST_DEBUG ("got segment event %" GST_PTR_FORMAT, event); + gst_event_copy_segment (event, &segment); + if (fake_applied_rate_value) + segment.applied_rate = fake_applied_rate_value; + if (fake_rate_value) + segment.rate = fake_rate_value; + gst_event_unref (event); + info->data = gst_event_new_segment (&segment); + GST_DEBUG ("forwarding segment event %" GST_PTR_FORMAT, + GST_EVENT (info->data)); + } + + return GST_PAD_PROBE_OK; +} + +static void +attach_rate_tweaking_probe (void) +{ + GstRTSPContext *ctx; + GstRTSPMedia *media; + GstRTSPStream *stream; + GstPad *srcpad; + + fail_unless ((ctx = gst_rtsp_context_get_current ()) != NULL); + + media = ctx->media; + fail_unless (media != NULL); + stream = gst_rtsp_media_get_stream (media, 0); + fail_unless (stream != NULL); + + srcpad = gst_rtsp_stream_get_srcpad (stream); + fail_unless (srcpad != NULL); + + GST_DEBUG ("adding rate_tweaking_probe"); + + gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + rate_tweaking_probe, NULL, NULL); +} + +static void +do_test_scale_and_speed (const gchar * scale, const gchar * speed) +{ + GstRTSPClient *client; + GstRTSPMessage request = { 0, }; + gchar *str; + GstRTSPContext ctx = { NULL }; + + client = setup_multicast_client (1); + + ctx.client = client; + ctx.auth = gst_rtsp_auth_new (); + ctx.token = + gst_rtsp_token_new (GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS, + G_TYPE_BOOLEAN, TRUE, GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING, + "user", NULL); + gst_rtsp_context_push_current (&ctx); + + expected_session_timeout = 20; + g_signal_connect (G_OBJECT (client), "new-session", + G_CALLBACK (new_session_cb), NULL); + + fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP, + "rtsp://localhost/test/stream=0") == GST_RTSP_OK); + str = g_strdup_printf ("%d", cseq); + gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str); + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT, + "RTP/AVP;multicast"); + expected_transport = "RTP/AVP;multicast;destination=233.252.0.1;" + "ttl=1;port=5000-5001;mode=\"PLAY\""; + gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL); + fail_unless (gst_rtsp_client_handle_message (client, + &request) == GST_RTSP_OK); + gst_rtsp_message_unset (&request); + expected_transport = NULL; + expected_session_timeout = 60; + + if (fake_applied_rate_value || fake_rate_value) + attach_rate_tweaking_probe (); + + fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY, + "rtsp://localhost/test") == GST_RTSP_OK); + str = g_strdup_printf ("%d", cseq); + gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str); + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id); + + if (scale != NULL) + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SCALE, scale); + if (speed != NULL) + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SPEED, speed); + + gst_rtsp_client_set_send_func (client, test_response_scale_speed, NULL, NULL); + fail_unless (gst_rtsp_client_handle_message (client, + &request) == GST_RTSP_OK); + gst_rtsp_message_unset (&request); + + send_teardown (client); + teardown_client (client); + g_object_unref (ctx.auth); + gst_rtsp_token_unref (ctx.token); + gst_rtsp_context_pop_current (&ctx); + +} + +GST_START_TEST (test_scale_and_speed) +{ + /* no scale/speed requested, no scale/speed should be received */ + expected_scale_header = NULL; + expected_speed_header = NULL; + do_test_scale_and_speed (NULL, NULL); + + /* scale requested, scale should be received */ + fake_applied_rate_value = 2; + fake_rate_value = 1; + expected_scale_header = "2.000"; + expected_speed_header = NULL; + do_test_scale_and_speed ("2.000", NULL); + + /* speed requested, speed should be received */ + fake_applied_rate_value = 0; + fake_rate_value = 0; + expected_scale_header = NULL; + expected_speed_header = "2.000"; + do_test_scale_and_speed (NULL, "2.000"); + + /* both requested, both should be received */ + fake_applied_rate_value = 2; + fake_rate_value = 2; + expected_scale_header = "2.000"; + expected_speed_header = "2.000"; + do_test_scale_and_speed ("2", "2"); + + /* scale requested but media doesn't handle scaling so both should be + * received, with scale set to 1.000 and speed set to (requested scale + * requested speed) */ + fake_applied_rate_value = 0; + fake_rate_value = 5; + expected_scale_header = "1.000"; + expected_speed_header = "5.000"; + do_test_scale_and_speed ("5", NULL); + + /* both requested but media only handles scaling so both should be received, + * with scale set to (requested scale * requested speed) and speed set to 1.00 + */ + fake_rate_value = 1.000; + fake_applied_rate_value = 4.000; + expected_scale_header = "4.000"; + expected_speed_header = "1.000"; + do_test_scale_and_speed ("2", "2"); +} + +GST_END_TEST static Suite * rtspclient_suite (void) { Suite *s = suite_create ("rtspclient"); @@ -1759,6 +1967,7 @@ rtspclient_suite (void) tcase_add_test (tc, test_client_multicast_max_ttl_first_client); tcase_add_test (tc, test_client_multicast_max_ttl_second_client); tcase_add_test (tc, test_client_multicast_invalid_ttl); + tcase_add_test (tc, test_scale_and_speed); return s; } |