summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBranko Subasic <branko@subasic.net>2018-10-12 08:53:04 +0200
committerMathieu Duponchelle <mathieu@centricular.com>2019-06-04 14:32:51 +0200
commitbc745896012a72f327ad1d135d695336b88bd1d3 (patch)
tree9ddbf7f0e222f564a04a15f7c9c89a0ce0546884
parent65d9aa327cd1844934836249cd4463edf09c725d (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.c148
-rw-r--r--gst/rtsp-server/rtsp-media.c55
-rw-r--r--gst/rtsp-server/rtsp-media.h3
-rw-r--r--tests/check/gst/client.c211
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;
}