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
This commit is contained in:
Branko Subasic 2018-10-12 08:53:04 +02:00 committed by Mathieu Duponchelle
parent 65d9aa327c
commit bc74589601
4 changed files with 386 additions and 33 deletions

View file

@ -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;
}
adjust_play_mode_failed:
scale_speed_failed:
{
GST_ERROR ("client %p: sub class returned bad code (%d)", client, status);
if (range != NULL)
gst_rtsp_range_free (range);
return status;
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,
rtsp_status_code);
if (range != NULL)
gst_rtsp_range_free (range);
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

View file

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

View file

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

View file

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