rtsp-media: allow specifying rate when seeking
authorBranko Subasic <branko@subasic.net>
Thu, 27 Sep 2018 17:09:01 +0000 (19:09 +0200)
committerMathieu Duponchelle <mathieu@centricular.com>
Tue, 4 Jun 2019 12:32:51 +0000 (14:32 +0200)
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
gst/rtsp-server/rtsp-media.h
gst/rtsp-server/rtsp-stream.c
gst/rtsp-server/rtsp-stream.h
tests/check/gst/media.c

index b238561..e03359e 100644 (file)
@@ -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)
 {
index c7728c6..0af6574 100644 (file)
@@ -388,6 +388,12 @@ gboolean              gst_rtsp_media_seek_full        (GstRTSPMedia *media,
                                                        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);
 
 GST_RTSP_SERVER_API
@@ -396,6 +402,11 @@ gchar *               gst_rtsp_media_get_range_string (GstRTSPMedia *media,
                                                        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);
 
index 68a1202..1c88cef 100644 (file)
@@ -4128,6 +4128,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
  *
index 7910bb0..424a023 100644 (file)
@@ -199,6 +199,11 @@ gboolean          gst_rtsp_stream_get_rtpinfo      (GstRTSPStream *stream,
                                                     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);
 
 GST_RTSP_SERVER_API
index 5ae704c..bcde9c3 100644 (file)
@@ -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);