From 421ac851504812ad640c58f92c04123254905b9b Mon Sep 17 00:00:00 2001 From: Branko Subasic Date: Thu, 27 Sep 2018 19:09:01 +0200 Subject: [PATCH] rtsp-media: allow specifying rate when seeking Add new function gst_rtsp_media_seek_full_with_rate() which allows the caller to specify the rate for the seek. Also added functions in rtsp-stream and rtsp-media for retreiving current rate and applied rate. https://bugzilla.gnome.org/show_bug.cgi?id=754575 --- gst/rtsp-server/rtsp-media.c | 136 ++++++++++++++++++++++++++-------- gst/rtsp-server/rtsp-media.h | 11 +++ gst/rtsp-server/rtsp-stream.c | 64 ++++++++++++++++ gst/rtsp-server/rtsp-stream.h | 5 ++ tests/check/gst/media.c | 25 ++++++- 5 files changed, 209 insertions(+), 32 deletions(-) diff --git a/gst/rtsp-server/rtsp-media.c b/gst/rtsp-server/rtsp-media.c index b238561678..e03359e66d 100644 --- a/gst/rtsp-server/rtsp-media.c +++ b/gst/rtsp-server/rtsp-media.c @@ -50,7 +50,8 @@ * #GstRTSPSession and #GstRTSPSessionMedia. * * The state of the media can be controlled with gst_rtsp_media_set_state (). - * Seeking can be done with gst_rtsp_media_seek(). + * Seeking can be done with gst_rtsp_media_seek(), or gst_rtsp_media_seek_full() + * or gst_rtsp_media_seek_full_with_rate() for finer control of the seek. * * With gst_rtsp_media_unprepare() the pipeline is stopped and shut down. When * gst_rtsp_media_set_eos_shutdown() an EOS will be sent to the pipeline to @@ -2554,6 +2555,51 @@ conversion_failed: } } +/** + * gst_rtsp_media_get_rates: + * @media: a #GstRTSPMedia + * @rate (allow-none): the rate of the current segment + * @applied_rate (allow-none): the applied_rate of the current segment + * + * Get the rate and applied_rate of the current segment. + * + * Returns: %FALSE if looking up the rate and applied rate failed. Otherwise + * %TRUE is returned and @rate and @applied_rate are set to the rate and + * applied_rate of the current segment. + * Since: 1.18 + */ +gboolean +gst_rtsp_media_get_rates (GstRTSPMedia * media, gdouble * rate, + gdouble * applied_rate) +{ + GstRTSPMediaPrivate *priv; + GstRTSPStream *stream; + gboolean result = TRUE; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + + if (!rate && !applied_rate) { + GST_WARNING_OBJECT (media, "rate and applied_rate are both NULL"); + return FALSE; + } + + priv = media->priv; + + g_mutex_lock (&priv->lock); + + g_assert (priv->streams->len > 0); + stream = g_ptr_array_index (priv->streams, 0); + if (!gst_rtsp_stream_get_rates (stream, rate, applied_rate)) { + GST_WARNING_OBJECT (media, + "failed to obtain rate and applied_rate from first stream"); + result = FALSE; + } + + g_mutex_unlock (&priv->lock); + + return result; +} + static void stream_update_blocked (GstRTSPStream * stream, GstRTSPMedia * media) { @@ -2635,22 +2681,24 @@ gst_rtsp_media_get_status (GstRTSPMedia * media) } /** - * gst_rtsp_media_seek_full: + * gst_rtsp_media_seek_full_with_rate: * @media: a #GstRTSPMedia * @range: (transfer none): a #GstRTSPTimeRange * @flags: The minimal set of #GstSeekFlags to use + * @rate: the rate to use in the seek * - * Seek the pipeline of @media to @range. @media must be prepared with - * gst_rtsp_media_prepare(). In order to perform the seek operation, - * the pipeline must contain all needed transport parts (transport sinks). + * Seek the pipeline of @media to @range with the given @flags and @rate. + * @media must be prepared with gst_rtsp_media_prepare(). + * In order to perform the seek operation, the pipeline must contain all + * needed transport parts (transport sinks). * * Returns: %TRUE on success. * - * Since: 1.14 + * Since: 1.18 */ gboolean -gst_rtsp_media_seek_full (GstRTSPMedia * media, GstRTSPTimeRange * range, - GstSeekFlags flags) +gst_rtsp_media_seek_full_with_rate (GstRTSPMedia * media, + GstRTSPTimeRange * range, GstSeekFlags flags, gdouble rate) { GstRTSPMediaClass *klass; GstRTSPMediaPrivate *priv; @@ -2658,12 +2706,15 @@ gst_rtsp_media_seek_full (GstRTSPMedia * media, GstRTSPTimeRange * range, GstClockTime start, stop; GstSeekType start_type, stop_type; gint64 current_position; + gboolean force_seek; klass = GST_RTSP_MEDIA_GET_CLASS (media); g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); - g_return_val_if_fail (range != NULL, FALSE); - g_return_val_if_fail (klass->convert_range != NULL, FALSE); + /* if there's a range then klass->convert_range must be set */ + g_return_val_if_fail (range == NULL || klass->convert_range != NULL, FALSE); + + GST_DEBUG ("flags=%x rate=%f", flags, rate); priv = media->priv; @@ -2689,15 +2740,21 @@ gst_rtsp_media_seek_full (GstRTSPMedia * media, GstRTSPTimeRange * range, } start_type = stop_type = GST_SEEK_TYPE_NONE; + start = stop = GST_CLOCK_TIME_NONE; - if (!klass->convert_range (media, range, GST_RTSP_RANGE_NPT)) - goto not_supported; - gst_rtsp_range_get_times (range, &start, &stop); + /* if caller provided a range convert it to NPT format + * if no range provided the seek is assumed to be the same position but with + * e.g. the rate changed */ + if (range != NULL) { + if (!klass->convert_range (media, range, GST_RTSP_RANGE_NPT)) + goto not_supported; + gst_rtsp_range_get_times (range, &start, &stop); - GST_INFO ("got %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, - GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); - GST_INFO ("current %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, - GST_TIME_ARGS (priv->range_start), GST_TIME_ARGS (priv->range_stop)); + GST_INFO ("got %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); + GST_INFO ("current %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, + GST_TIME_ARGS (priv->range_start), GST_TIME_ARGS (priv->range_stop)); + } current_position = -1; if (klass->query_position) @@ -2713,24 +2770,24 @@ gst_rtsp_media_seek_full (GstRTSPMedia * media, GstRTSPTimeRange * range, else if (stop != GST_CLOCK_TIME_NONE) stop_type = GST_SEEK_TYPE_SET; - if (start != GST_CLOCK_TIME_NONE || stop != GST_CLOCK_TIME_NONE) { - gboolean had_flags = flags != 0; + /* we force a seek if any seek flag is set, or if the the rate + * is non-standard, i.e. not 1.0 */ + force_seek = flags != GST_SEEK_FLAG_NONE || rate != 1.0; + + if (start != GST_CLOCK_TIME_NONE || stop != GST_CLOCK_TIME_NONE || force_seek) { + gboolean had_flags = flags != GST_SEEK_FLAG_NONE; 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. */ - if (had_flags) - flags |= GST_SEEK_FLAG_FLUSH; - else - flags = GST_SEEK_FLAG_FLUSH; - + 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 * so we end up at exactly the same position after the seek. */ - if (range->min.type == GST_RTSP_TIME_END) { /* Yepp, that's right! */ + if (range == NULL || range->min.type == GST_RTSP_TIME_END) { if (current_position == -1) { GST_WARNING ("current position unknown"); } else { @@ -2748,14 +2805,14 @@ gst_rtsp_media_seek_full (GstRTSPMedia * media, GstRTSPTimeRange * range, flags |= GST_SEEK_FLAG_KEY_UNIT; } - if (start == current_position && stop_type == GST_SEEK_TYPE_NONE) { - GST_DEBUG ("not seeking because no position change"); + if (start == current_position && stop_type == GST_SEEK_TYPE_NONE && + !force_seek) { + GST_DEBUG ("no position change, no flags set by caller, so not seeking"); res = TRUE; } else { gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); - /* FIXME, we only do forwards playback, no trick modes yet */ - res = gst_element_seek (priv->pipeline, 1.0, GST_FORMAT_TIME, + res = gst_element_seek (priv->pipeline, rate, GST_FORMAT_TIME, flags, start_type, start, stop_type, stop); /* and block for the seek to complete */ @@ -2819,6 +2876,23 @@ preroll_failed: } } +/** + * 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 with the given @flags. + * @media must be prepared with gst_rtsp_media_prepare(). + * + * Returns: %TRUE on success. + */ +gboolean +gst_rtsp_media_seek_full (GstRTSPMedia * media, GstRTSPTimeRange * range, + GstSeekFlags flags) +{ + return gst_rtsp_media_seek_full_with_rate (media, range, flags, 1.0); +} /** * gst_rtsp_media_seek: @@ -2833,10 +2907,10 @@ preroll_failed: gboolean gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) { - return gst_rtsp_media_seek_full (media, range, 0); + return gst_rtsp_media_seek_full_with_rate (media, range, GST_SEEK_FLAG_NONE, + 1.0); } - static void stream_collect_blocking (GstRTSPStream * stream, gboolean * blocked) { diff --git a/gst/rtsp-server/rtsp-media.h b/gst/rtsp-server/rtsp-media.h index c7728c6fe4..0af6574682 100644 --- a/gst/rtsp-server/rtsp-media.h +++ b/gst/rtsp-server/rtsp-media.h @@ -387,6 +387,12 @@ gboolean gst_rtsp_media_seek_full (GstRTSPMedia *media, GstRTSPTimeRange *range, GstSeekFlags flags); +GST_RTSP_SERVER_API +gboolean gst_rtsp_media_seek_full_with_rate (GstRTSPMedia *media, + GstRTSPTimeRange *range, + GstSeekFlags flags, + gdouble rate); + GST_RTSP_SERVER_API GstClockTimeDiff gst_rtsp_media_seekable (GstRTSPMedia *media); @@ -395,6 +401,11 @@ gchar * gst_rtsp_media_get_range_string (GstRTSPMedia *media, gboolean play, GstRTSPRangeUnit unit); +GST_RTSP_SERVER_API +gboolean gst_rtsp_media_get_rates (GstRTSPMedia * media, + gdouble * rate, + gdouble * applied_rate); + GST_RTSP_SERVER_API gboolean gst_rtsp_media_set_state (GstRTSPMedia *media, GstState state, GPtrArray *transports); diff --git a/gst/rtsp-server/rtsp-stream.c b/gst/rtsp-server/rtsp-stream.c index 68a1202fbf..1c88cefb11 100644 --- a/gst/rtsp-server/rtsp-stream.c +++ b/gst/rtsp-server/rtsp-stream.c @@ -4127,6 +4127,70 @@ no_stats: } } +/** + * gst_rtsp_stream_get_rates: + * @stream: a #GstRTSPStream + * @rate: (allow-none): the configured rate + * @applied_rate: (allow-none): the configured applied_rate + * + * Retrieve the current rate and/or applied_rate. + * + * Returns: %TRUE if rate and/or applied_rate could be determined. + * Since: 1.18 + */ +gboolean +gst_rtsp_stream_get_rates (GstRTSPStream * stream, gdouble * rate, + gdouble * applied_rate) +{ + GstRTSPStreamPrivate *priv; + GstEvent *event; + const GstSegment *segment; + + g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE); + + if (!rate && !applied_rate) { + GST_WARNING_OBJECT (stream, "rate and applied_rate are both NULL"); + return FALSE; + } + + priv = stream->priv; + + g_mutex_lock (&priv->lock); + + if (!priv->send_rtp_sink) + goto no_rtp_sink_pad; + + event = gst_pad_get_sticky_event (priv->send_rtp_sink, GST_EVENT_SEGMENT, 0); + if (!event) + goto no_sticky_event; + + gst_event_parse_segment (event, &segment); + if (rate) + *rate = segment->rate; + if (applied_rate) + *applied_rate = segment->applied_rate; + + gst_event_unref (event); + g_mutex_unlock (&priv->lock); + + return TRUE; + +/* ERRORS */ +no_rtp_sink_pad: + { + GST_WARNING_OBJECT (stream, "no send_rtp_sink pad yet"); + g_mutex_unlock (&priv->lock); + return FALSE; + } +no_sticky_event: + { + GST_WARNING_OBJECT (stream, "no segment event on send_rtp_sink pad"); + g_mutex_unlock (&priv->lock); + return FALSE; + } + +} + /** * gst_rtsp_stream_get_caps: * @stream: a #GstRTSPStream diff --git a/gst/rtsp-server/rtsp-stream.h b/gst/rtsp-server/rtsp-stream.h index 7910bb05d7..424a0230ee 100644 --- a/gst/rtsp-server/rtsp-stream.h +++ b/gst/rtsp-server/rtsp-stream.h @@ -198,6 +198,11 @@ gboolean gst_rtsp_stream_get_rtpinfo (GstRTSPStream *stream, guint *clock_rate, GstClockTime *running_time); +GST_RTSP_SERVER_API +gboolean gst_rtsp_stream_get_rates (GstRTSPStream * stream, + gdouble * rate, + gdouble * applied_rate); + GST_RTSP_SERVER_API GstCaps * gst_rtsp_stream_get_caps (GstRTSPStream *stream); diff --git a/tests/check/gst/media.c b/tests/check/gst/media.c index 5ae704ce9b..bcde9c30f9 100644 --- a/tests/check/gst/media.c +++ b/tests/check/gst/media.c @@ -60,6 +60,8 @@ GST_START_TEST (test_media_seek) GstRTSPThreadPool *pool; GstRTSPThread *thread; GstRTSPTransport *transport; + gdouble rate = 0; + gdouble applied_rate = 0; factory = gst_rtsp_media_factory_new (); fail_if (gst_rtsp_media_factory_is_shared (factory)); @@ -98,9 +100,30 @@ GST_START_TEST (test_media_seek) str = gst_rtsp_media_get_range_string (media, FALSE, GST_RTSP_RANGE_NPT); fail_unless (g_str_equal (str, "npt=5-")); + g_free (str); + + /* seeking without rate should result in rate == 1.0 */ + fail_unless (gst_rtsp_media_seek (media, range)); + fail_unless (gst_rtsp_media_get_rates (media, &rate, &applied_rate)); + fail_unless (rate == 1.0); + fail_unless (applied_rate == 1.0); + + /* seeking with rate set to 1.5 should result in rate == 1.5 */ + fail_unless (gst_rtsp_media_seek_full_with_rate (media, range, + GST_SEEK_FLAG_NONE, 1.5)); + fail_unless (gst_rtsp_media_get_rates (media, &rate, &applied_rate)); + fail_unless (rate == 1.5); + fail_unless (applied_rate == 1.0); + + /* seeking with rate set to -2.0 should result in rate == -2.0 */ + fail_unless (gst_rtsp_range_parse ("npt=5-10", &range) == GST_RTSP_OK); + fail_unless (gst_rtsp_media_seek_full_with_rate (media, range, + GST_SEEK_FLAG_NONE, -2.0)); + fail_unless (gst_rtsp_media_get_rates (media, &rate, &applied_rate)); + fail_unless (rate == -2.0); + fail_unless (applied_rate == 1.0); gst_rtsp_range_free (range); - g_free (str); fail_unless (gst_rtsp_media_unprepare (media)); g_object_unref (media);