rtspsrc: expose and implement is-live property
authorMathieu Duponchelle <mathieu@centricular.com>
Sat, 27 Jul 2019 02:05:01 +0000 (04:05 +0200)
committerMathieu Duponchelle <mduponchelle1@gmail.com>
Tue, 6 Aug 2019 22:45:37 +0000 (22:45 +0000)
This is useful to support the ONVIF case: when is-live is set to
FALSE and onvif-rate-control is no, the client can control the
rate of delivery and arrange for the server to block and still
keep sending when unblocked, without requiring back and forth
PAUSE / PLAY requests. This enables, amongst other things, fast
frame stepping on the client side.

When is-live is FALSE, we don't use a manager at all. This case
was actually already pretty well handled by the current code. The
standard manager, rtpbin, is simply no longer needed in this case.

Applications can instantiate a downloadbuffer after rtspsrc if
needed.

gst/rtsp/gstrtspsrc.c
gst/rtsp/gstrtspsrc.h

index 5eac4db..e8a2c46 100644 (file)
@@ -282,6 +282,7 @@ gst_rtsp_backchannel_get_type (void)
 #define DEFAULT_TEARDOWN_TIMEOUT  (100 * GST_MSECOND)
 #define DEFAULT_ONVIF_MODE FALSE
 #define DEFAULT_ONVIF_RATE_CONTROL TRUE
+#define DEFAULT_IS_LIVE TRUE
 
 enum
 {
@@ -328,7 +329,8 @@ enum
   PROP_BACKCHANNEL,
   PROP_TEARDOWN_TIMEOUT,
   PROP_ONVIF_MODE,
-  PROP_ONVIF_RATE_CONTROL
+  PROP_ONVIF_RATE_CONTROL,
+  PROP_IS_LIVE
 };
 
 #define GST_TYPE_RTSP_NAT_METHOD (gst_rtsp_nat_method_get_type())
@@ -978,6 +980,22 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass)
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   /**
+   * GstRtspSrc:is-live
+   *
+   * Whether to act as a live source. This is useful in combination with
+   * #GstRtspSrc:onvif-rate-control set to %FALSE and usage of the TCP
+   * protocol. In that situation, data delivery rate can be entirely
+   * controlled from the client side, enabling features such as frame
+   * stepping and instantaneous rate changes.
+   *
+   * Since: 1.18
+   */
+  g_object_class_install_property (gobject_class, PROP_IS_LIVE,
+      g_param_spec_boolean ("is-live", "Is live",
+          "Whether to act as a live source",
+          DEFAULT_IS_LIVE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
    * GstRTSPSrc::handle-request:
    * @rtspsrc: a #GstRTSPSrc
    * @request: a #GstRTSPMessage
@@ -1379,6 +1397,7 @@ gst_rtspsrc_init (GstRTSPSrc * src)
   src->teardown_timeout = DEFAULT_TEARDOWN_TIMEOUT;
   src->onvif_mode = DEFAULT_ONVIF_MODE;
   src->onvif_rate_control = DEFAULT_ONVIF_RATE_CONTROL;
+  src->is_live = DEFAULT_IS_LIVE;
 
   /* get a list of all extensions */
   src->extensions = gst_rtsp_ext_list_get ();
@@ -1723,6 +1742,9 @@ gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value,
     case PROP_ONVIF_RATE_CONTROL:
       rtspsrc->onvif_rate_control = g_value_get_boolean (value);
       break;
+    case PROP_IS_LIVE:
+      rtspsrc->is_live = g_value_get_boolean (value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -1893,6 +1915,9 @@ gst_rtspsrc_get_property (GObject * object, guint prop_id, GValue * value,
     case PROP_ONVIF_RATE_CONTROL:
       g_value_set_boolean (value, rtspsrc->onvif_rate_control);
       break;
+    case PROP_IS_LIVE:
+      g_value_set_boolean (value, rtspsrc->is_live);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -2853,11 +2878,14 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event)
     stream->discont = TRUE;
   }
 
-  /* and continue playing if needed */
+  /* and continue playing if needed. If we are not acting as a live source,
+   * then only the RTSP PLAYING state, set earlier, matters. */
   GST_OBJECT_LOCK (src);
-  playing = (GST_STATE_PENDING (src) == GST_STATE_VOID_PENDING
-      && GST_STATE (src) == GST_STATE_PLAYING)
-      || (GST_STATE_PENDING (src) == GST_STATE_PLAYING);
+  if (src->is_live) {
+    playing = (GST_STATE_PENDING (src) == GST_STATE_VOID_PENDING
+        && GST_STATE (src) == GST_STATE_PLAYING)
+        || (GST_STATE_PENDING (src) == GST_STATE_PLAYING);
+  }
   GST_OBJECT_UNLOCK (src);
 
   if (src->version >= GST_RTSP_VERSION_2_0) {
@@ -3029,7 +3057,7 @@ gst_rtspsrc_handle_internal_src_query (GstPad * pad, GstObject * parent,
     {
       /* we are live with a min latency of 0 and unlimited max latency, this
        * result will be updated by the session manager if there is any. */
-      gst_query_set_latency (query, TRUE, 0, -1);
+      gst_query_set_latency (query, src->is_live, 0, -1);
       break;
     }
     default:
@@ -3502,7 +3530,10 @@ on_timeout (GObject * session, GObject * source, GstRTSPStream * stream)
               "stream-number", G_TYPE_INT, stream->id, "ssrc", G_TYPE_UINT,
               stream->ssrc, NULL)));
 
-  on_timeout_common (session, source, stream);
+  /* In non-live mode, timeouts can occur if we are PAUSED, this doesn't mean
+   * the stream is EOS, it may simply be blocked */
+  if (src->is_live || !src->interleaved)
+    on_timeout_common (session, source, stream);
 }
 
 static void
@@ -3830,6 +3861,9 @@ gst_rtspsrc_stream_configure_manager (GstRTSPSrc * src, GstRTSPStream * stream,
   gchar *name;
   GstStateChangeReturn ret;
 
+  if (!src->is_live)
+    goto use_no_manager;
+
   /* find a manager */
   if (gst_rtsp_transport_get_manager (transport->trans, &manager, 0) < 0)
     goto no_manager;
@@ -5338,6 +5372,11 @@ gst_rtspsrc_handle_data (GstRTSPSrc * src, GstRTSPMessage * message)
       GstRTSPStream *ostream = (GstRTSPStream *) streams->data;
       GstCaps *caps;
 
+      /* Activate in advance so that the stream-start event is registered */
+      if (stream->srcpad) {
+        gst_pad_set_active (stream->srcpad, TRUE);
+      }
+
       stream_id =
           g_strdup_printf ("%s/%d", g_checksum_get_string (cs), ostream->id);
       event = gst_event_new_stream_start (stream_id);
@@ -7986,6 +8025,12 @@ gst_rtspsrc_open (GstRTSPSrc * src, gboolean async)
   if ((ret = gst_rtspsrc_open_from_sdp (src, src->sdp, async)) < 0)
     goto open_failed;
 
+  if (src->initial_seek) {
+    if (!gst_rtspsrc_perform_seek (src, src->initial_seek))
+      goto initial_seek_failed;
+    gst_event_replace (&src->initial_seek, NULL);
+  }
+
 done:
   if (async)
     gst_rtspsrc_loop_end_cmd (src, CMD_OPEN, ret);
@@ -8005,6 +8050,13 @@ open_failed:
     src->open_error = TRUE;
     goto done;
   }
+initial_seek_failed:
+  {
+    GST_WARNING_OBJECT (src, "Failed to perform initial seek");
+    ret = GST_RTSP_ERROR;
+    src->open_error = TRUE;
+    goto done;
+  }
 }
 
 static GstRTSPResult
@@ -9028,17 +9080,22 @@ gst_rtspsrc_change_state (GstElement * element, GstStateChange transition)
       /* first attempt, don't ignore timeouts */
       rtspsrc->ignore_timeout = FALSE;
       rtspsrc->open_error = FALSE;
-      gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_OPEN, 0);
+      if (rtspsrc->is_live)
+        gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_OPEN, 0);
+      else
+        gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_PLAY, 0);
       break;
     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
       set_manager_buffer_mode (rtspsrc);
       /* fall-through */
     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
-      /* unblock the tcp tasks and make the loop waiting */
-      if (gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_WAIT, CMD_LOOP)) {
-        /* make sure it is waiting before we send PAUSE or PLAY below */
-        GST_RTSP_STREAM_LOCK (rtspsrc);
-        GST_RTSP_STREAM_UNLOCK (rtspsrc);
+      if (rtspsrc->is_live) {
+        /* unblock the tcp tasks and make the loop waiting */
+        if (gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_WAIT, CMD_LOOP)) {
+          /* make sure it is waiting before we send PAUSE or PLAY below */
+          GST_RTSP_STREAM_LOCK (rtspsrc);
+          GST_RTSP_STREAM_UNLOCK (rtspsrc);
+        }
       }
       break;
     case GST_STATE_CHANGE_PAUSED_TO_READY:
@@ -9056,16 +9113,22 @@ gst_rtspsrc_change_state (GstElement * element, GstStateChange transition)
       ret = GST_STATE_CHANGE_SUCCESS;
       break;
     case GST_STATE_CHANGE_READY_TO_PAUSED:
-      ret = GST_STATE_CHANGE_NO_PREROLL;
+      if (rtspsrc->is_live)
+        ret = GST_STATE_CHANGE_NO_PREROLL;
+      else
+        ret = GST_STATE_CHANGE_SUCCESS;
       break;
     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
-      gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_PLAY, 0);
+      if (rtspsrc->is_live)
+        gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_PLAY, 0);
       ret = GST_STATE_CHANGE_SUCCESS;
       break;
     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
-      /* send pause request and keep the idle task around */
-      gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_PAUSE, CMD_LOOP);
-      ret = GST_STATE_CHANGE_NO_PREROLL;
+      if (rtspsrc->is_live) {
+        /* send pause request and keep the idle task around */
+        gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_PAUSE, CMD_LOOP);
+      }
+      ret = GST_STATE_CHANGE_SUCCESS;
       break;
     case GST_STATE_CHANGE_PAUSED_TO_READY:
       gst_rtspsrc_loop_send_cmd_and_wait (rtspsrc, CMD_CLOSE, CMD_ALL,
@@ -9109,8 +9172,14 @@ gst_rtspsrc_send_event (GstElement * element, GstEvent * event)
   rtspsrc = GST_RTSPSRC (element);
 
   if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK) {
-    res = gst_rtspsrc_perform_seek (rtspsrc, event);
-    gst_event_unref (event);
+    if (rtspsrc->state >= GST_RTSP_STATE_READY) {
+      res = gst_rtspsrc_perform_seek (rtspsrc, event);
+      gst_event_unref (event);
+    } else {
+      /* Store for later use */
+      res = TRUE;
+      rtspsrc->initial_seek = event;
+    }
   } else if (GST_EVENT_IS_DOWNSTREAM (event)) {
     res = gst_rtspsrc_push_event (rtspsrc, event);
   } else {
index c20b6f2..4076040 100644 (file)
@@ -277,6 +277,7 @@ struct _GstRTSPSrc {
   GstClockTime      teardown_timeout;
   gboolean          onvif_mode;
   gboolean          onvif_rate_control;
+  gboolean          is_live;
 
   /* state */
   GstRTSPState       state;
@@ -320,6 +321,8 @@ struct _GstRTSPSrc {
 
   GstRTSPVersion default_version;
   GstRTSPVersion version;
+
+  GstEvent *initial_seek;
 };
 
 struct _GstRTSPSrcClass {