gst/base/gstbasesrc.c: Move some stuff around and cleanup things.
authorWim Taymans <wim.taymans@gmail.com>
Thu, 27 Oct 2005 19:37:25 +0000 (19:37 +0000)
committerWim Taymans <wim.taymans@gmail.com>
Thu, 27 Oct 2005 19:37:25 +0000 (19:37 +0000)
Original commit message from CVS:
* gst/base/gstbasesrc.c: (gst_base_src_class_init),
(gst_base_src_init), (gst_base_src_query),
(gst_base_src_default_newsegment),
(gst_base_src_configure_segment), (gst_base_src_do_seek),
(gst_base_src_send_event), (gst_base_src_event_handler),
(gst_base_src_pad_get_range), (gst_base_src_loop),
(gst_base_src_unlock), (gst_base_src_default_negotiate),
(gst_base_src_start), (gst_base_src_deactivate),
(gst_base_src_activate_push), (gst_base_src_change_state):
Move some stuff around and cleanup things.

ChangeLog
gst/base/gstbasesrc.c
libs/gst/base/gstbasesrc.c

index 457b976..bc28a55 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2005-10-27  Wim Taymans  <wim@fluendo.com>
+
+       * gst/base/gstbasesrc.c: (gst_base_src_class_init),
+       (gst_base_src_init), (gst_base_src_query),
+       (gst_base_src_default_newsegment),
+       (gst_base_src_configure_segment), (gst_base_src_do_seek),
+       (gst_base_src_send_event), (gst_base_src_event_handler),
+       (gst_base_src_pad_get_range), (gst_base_src_loop),
+       (gst_base_src_unlock), (gst_base_src_default_negotiate),
+       (gst_base_src_start), (gst_base_src_deactivate),
+       (gst_base_src_activate_push), (gst_base_src_change_state):
+       Move some stuff around and cleanup things.
+
 2005-10-27  Tim-Philipp Müller  <tim at centricular dot net>
 
        * gst/base/gstbasesrc.c: (gst_base_src_query):
index b86ee46..2d8b0f7 100644 (file)
@@ -113,6 +113,7 @@ static void gst_base_src_set_property (GObject * object, guint prop_id,
 static void gst_base_src_get_property (GObject * object, guint prop_id,
     GValue * value, GParamSpec * pspec);
 static gboolean gst_base_src_event_handler (GstPad * pad, GstEvent * event);
+static gboolean gst_base_src_send_event (GstElement * elem, GstEvent * event);
 
 static gboolean gst_base_src_query (GstPad * pad, GstQuery * query);
 
@@ -167,6 +168,7 @@ gst_base_src_class_init (GstBaseSrcClass * klass)
 
   gstelement_class->change_state =
       GST_DEBUG_FUNCPTR (gst_base_src_change_state);
+  gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_base_src_send_event);
 
   klass->negotiate = gst_base_src_default_negotiate;
   klass->newsegment = gst_base_src_default_newsegment;
@@ -214,10 +216,6 @@ gst_base_src_init (GstBaseSrc * basesrc, gpointer g_class)
   GST_DEBUG_OBJECT (basesrc, "adding src pad");
   gst_element_add_pad (GST_ELEMENT (basesrc), pad);
 
-  basesrc->segment_loop = FALSE;
-  basesrc->segment_start = -1;
-  basesrc->segment_end = -1;
-  basesrc->need_newsegment = TRUE;
   basesrc->blocksize = DEFAULT_BLOCKSIZE;
   basesrc->clock_id = NULL;
 
@@ -392,10 +390,26 @@ gst_base_src_query (GstPad * pad, GstQuery * query)
 
     case GST_QUERY_SEEKING:
       gst_query_set_seeking (query, GST_FORMAT_BYTES,
-          src->seekable, src->segment_start, src->segment_end);
+          src->seekable, 0, src->size);
       res = TRUE;
       break;
 
+    case GST_QUERY_SEGMENT:
+    {
+      gint64 start, stop;
+
+      start = src->segment_start;
+      /* no end segment configured, current size then */
+      if ((stop = src->segment_end) == -1)
+        stop = src->size;
+
+      /* FIXME, we can't report our rate as we did not store it, d'oh!.
+       * Also, subclasses might want to support other formats. */
+      gst_query_set_segment (query, 1.0, GST_FORMAT_BYTES, start, stop);
+      res = TRUE;
+      break;
+    }
+
     case GST_QUERY_FORMATS:
       gst_query_set_formats (query, 3, GST_FORMAT_DEFAULT,
           GST_FORMAT_BYTES, GST_FORMAT_PERCENT);
@@ -421,12 +435,11 @@ gst_base_src_default_newsegment (GstBaseSrc * src)
   GstEvent *event;
 
   GST_DEBUG_OBJECT (src, "Sending newsegment from %" G_GINT64_FORMAT
-      " to %" G_GINT64_FORMAT, (gint64) src->segment_start,
-      (gint64) src->segment_end);
+      " to %" G_GINT64_FORMAT, src->segment_start, src->segment_end);
+
   event = gst_event_new_newsegment (FALSE, 1.0,
-      GST_FORMAT_BYTES,
-      (gint64) src->segment_start, (gint64) src->segment_end,
-      (gint64) src->segment_start);
+      GST_FORMAT_BYTES, src->segment_start, src->segment_end,
+      src->segment_start);
 
   return gst_pad_push_event (src->srcpad, event);
 }
@@ -445,45 +458,31 @@ gst_base_src_newsegment (GstBaseSrc * src)
   return result;
 }
 
+/* based on the event parameters configure the segment_start/stop
+ * times. Called with STREAM_LOCK.
+ */
 static gboolean
-gst_base_src_do_seek (GstBaseSrc * src, GstEvent * event)
+gst_base_src_configure_segment (GstBaseSrc * src, GstEvent * event)
 {
   gdouble rate;
   GstFormat format;
   GstSeekFlags flags;
   GstSeekType cur_type, stop_type;
   gint64 cur, stop;
-  gboolean flush;
-  gboolean update_stop = TRUE, update_start = TRUE;
+  gboolean update_stop, update_start;
 
   gst_event_parse_seek (event, &rate, &format, &flags,
       &cur_type, &cur, &stop_type, &stop);
 
-  /* get seek format */
-  if (format == GST_FORMAT_DEFAULT)
-    format = GST_FORMAT_BYTES;
-  /* we can only seek bytes */
-  if (format != GST_FORMAT_BYTES)
-    goto unsupported_seek;
-
-  flush = flags & GST_SEEK_FLAG_FLUSH;
-
-  /* get seek positions */
+  /* parse the loop flag */
+  /* FIXME, need to store other flags and rate too */
   src->segment_loop = (flags & GST_SEEK_FLAG_SEGMENT) != 0;
 
-  /* send flush start */
-  if (flush)
-    gst_pad_push_event (src->srcpad, gst_event_new_flush_start ());
-  else
-    gst_pad_pause_task (src->srcpad);
+  /* assume we'll update both start and stop values */
+  update_start = TRUE;
+  update_stop = TRUE;
 
-  /* unblock streaming thread */
-  gst_base_src_unlock (src);
-
-  /* grab streaming lock */
-  GST_STREAM_LOCK (src->srcpad);
-
-  /* perform the seek */
+  /* perform the seek, segment_start is never invalid */
   switch (cur_type) {
     case GST_SEEK_TYPE_NONE:
       /* no update to segment */
@@ -491,7 +490,7 @@ gst_base_src_do_seek (GstBaseSrc * src, GstEvent * event)
       update_start = FALSE;
       break;
     case GST_SEEK_TYPE_SET:
-      /* cur hold desired position */
+      /* cur holds desired position */
       break;
     case GST_SEEK_TYPE_CUR:
       /* add cur to currently configure segment */
@@ -503,64 +502,187 @@ gst_base_src_do_seek (GstBaseSrc * src, GstEvent * event)
       break;
   }
   /* bring in sane range */
-  cur = CLAMP (cur, 0, src->size);
+  if (src->size != -1)
+    cur = CLAMP (cur, 0, src->size);
+  else
+    cur = MAX (cur, 0);
 
+  /* segment_end can be -1 if we have not configured a stop. */
   switch (stop_type) {
     case GST_SEEK_TYPE_NONE:
       stop = src->segment_end;
       update_stop = FALSE;
       break;
     case GST_SEEK_TYPE_SET:
+      /* stop folds required value */
       break;
     case GST_SEEK_TYPE_CUR:
-      stop = src->segment_end + stop;
+      if (src->segment_end != -1)
+        stop = src->segment_end + stop;
+      else
+        stop = -1;
       break;
     case GST_SEEK_TYPE_END:
-      stop = src->size + stop;
+      if (src->size != -1)
+        stop = src->size + stop;
+      else
+        stop = -1;
       break;
   }
-  stop = CLAMP (stop, 0, src->size);
+
+  /* if we have a valid stop time, make sure it is clipped */
+  if (stop != -1) {
+    if (src->size != -1)
+      stop = CLAMP (stop, 0, src->size);
+    else
+      stop = MAX (stop, 0);
+  }
 
   src->segment_start = cur;
   src->segment_end = stop;
 
-  /* update our offset */
-  src->offset = cur;
+  /* update our offset if it was updated */
+  if (update_start)
+    src->offset = cur;
 
-  GST_DEBUG_OBJECT (src, "seek pending for segment from %" G_GINT64_FORMAT
+  GST_DEBUG_OBJECT (src, "segment configured from %" G_GINT64_FORMAT
       " to %" G_GINT64_FORMAT, src->segment_start, src->segment_end);
 
-  /* now make sure the newsegment will be send */
-  src->need_newsegment = TRUE;
+  return TRUE;
+}
+
+/* this code implements the seeking. It is a good example
+ * handling all cases (modulo the FIXMEs).
+ *
+ * A seek updates the currently configured segment_start
+ * and segment_stop values based on the SEEK_TYPE. If the
+ * segment_start value is updated, a seek to this new position
+ * should be performed.
+ *
+ * The seek can only be executed when we are not currently
+ * streaming any data, to make sure that this is the case, we
+ * acquire the STREAM_LOCK which is taken when we are in the
+ * _loop() function or when a getrange() is called. Normally
+ * we will not receive a seek if we are operating in pull mode
+ * though.
+ *
+ * When we are in the loop() function, we might be in the middle
+ * of pushing a buffer, which might block in a sink. To make sure
+ * that the push gets unblocked we push out a FLUSH_START event.
+ * Our loop function will get a WRONG_STATE return value from
+ * the push and will pause, effectively releasing the STREAM_LOCK.
+ *
+ * For a non-flushing seek, we pause the task, which might eventually
+ * release the STREAM_LOCK. We say eventually because when the sink
+ * blocks on the sample we might wait a very long time until the sink
+ * unblocks the sample. In any case we acquire the STREAM_LOCK and
+ * can continue the seek. A non-flushing seek is normally done in a 
+ * running pipeline to perform seamless playback.
+ *
+ * After updating the segment_start/stop values, we prepare for
+ * streaming again. We push out a FLUSH_STOP to make the peer pad
+ * accept data again and we start our task again.
+ *
+ * A segment seek posts a message on the bus saying that the playback
+ * of the segment started. We store the segment flag internally because
+ * when we reach the segment_stop we have to post a segment_done
+ * instead of EOS when doing a segment seek.
+ */
+static gboolean
+gst_base_src_do_seek (GstBaseSrc * src, GstEvent * event)
+{
+  gdouble rate;
+  GstFormat format;
+  GstSeekFlags flags;
+  gboolean flush;
+
+  gst_event_parse_seek (event, &rate, &format, &flags, NULL, NULL, NULL, NULL);
+
+  /* FIXME subclasses should be able to provide other formats */
+  /* get seek format */
+  if (format == GST_FORMAT_DEFAULT)
+    format = GST_FORMAT_BYTES;
+  /* we can only seek bytes */
+  if (format != GST_FORMAT_BYTES)
+    goto unsupported_seek;
+
+  flush = flags & GST_SEEK_FLAG_FLUSH;
+
+  /* send flush start */
+  if (flush)
+    gst_pad_push_event (src->srcpad, gst_event_new_flush_start ());
+  else
+    gst_pad_pause_task (src->srcpad);
 
+  /* unblock streaming thread */
+  gst_base_src_unlock (src);
+
+  /* grab streaming lock, this should eventually be possible, either
+   * because the task is paused or out streaming thread stopped 
+   * because our peer is flushing. */
+  GST_STREAM_LOCK (src->srcpad);
+
+  /* now configure the segment */
+  gst_base_src_configure_segment (src, event);
+
+  /* and prepare to continue streaming */
   if (flush)
-    /* send flush stop */
+    /* send flush stop, peer will accept data and events again. We
+     * are not yet providing data as we still have the STREAM_LOCK. */
     gst_pad_push_event (src->srcpad, gst_event_new_flush_stop ());
 
+  /* now make sure the newsegment will be send from the streaming
+   * thread. We could opt to send it here too. */
+  src->need_newsegment = TRUE;
+
   if (src->segment_loop) {
+    /* FIXME subclasses should be able to provide other formats */
     gst_element_post_message (GST_ELEMENT (src),
         gst_message_new_segment_start (GST_OBJECT (src), GST_FORMAT_BYTES,
-            cur));
+            src->segment_start));
   }
 
-  /* and restart the task */
+  /* and restart the task in case it got paused explicitely or by
+   * the FLUSH_START event we pushed out. */
   gst_pad_start_task (src->srcpad, (GstTaskFunction) gst_base_src_loop,
       src->srcpad);
 
+  /* and release the lock again so we can continue streaming */
   GST_STREAM_UNLOCK (src->srcpad);
 
-  gst_event_unref (event);
-
   return TRUE;
 
   /* ERROR */
 unsupported_seek:
   {
     GST_DEBUG_OBJECT (src, "invalid format, seek aborted.");
+
     return FALSE;
   }
 }
 
+/* all events send to this element directly
+ */
+static gboolean
+gst_base_src_send_event (GstElement * element, GstEvent * event)
+{
+  GstBaseSrc *src;
+  gboolean result;
+
+  src = GST_BASE_SRC (element);
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_SEEK:
+      result = gst_base_src_configure_segment (src, event);
+      break;
+    default:
+      result = FALSE;
+      break;
+  }
+
+  return result;
+}
+
 static gboolean
 gst_base_src_event_handler (GstPad * pad, GstEvent * event)
 {
@@ -568,31 +690,52 @@ gst_base_src_event_handler (GstPad * pad, GstEvent * event)
   GstBaseSrcClass *bclass;
   gboolean result;
 
-  src = GST_BASE_SRC (GST_PAD_PARENT (pad));
+  src = GST_BASE_SRC (gst_pad_get_parent (pad));
   bclass = GST_BASE_SRC_GET_CLASS (src);
 
-  if (bclass->event)
-    result = bclass->event (src, event);
+  if (bclass->event) {
+    if (!(result = bclass->event (src, event)))
+      goto subclass_failed;
+  }
 
   switch (GST_EVENT_TYPE (event)) {
     case GST_EVENT_SEEK:
-      if (!src->seekable) {
-        gst_event_unref (event);
-        return FALSE;
-      }
-      return gst_base_src_do_seek (src, event);
+      /* is normally called when in push mode */
+      if (!src->seekable)
+        goto not_seekable;
+
+      result = gst_base_src_do_seek (src, event);
+      break;
     case GST_EVENT_FLUSH_START:
-      /* cancel any blocking getrange */
-      gst_base_src_unlock (src);
+      /* cancel any blocking getrange, is normally called
+       * when in pull mode. */
+      result = gst_base_src_unlock (src);
       break;
     case GST_EVENT_FLUSH_STOP:
-      break;
     default:
+      result = TRUE;
       break;
   }
   gst_event_unref (event);
+  gst_object_unref (src);
 
-  return TRUE;
+  return result;
+
+  /* ERRORS */
+subclass_failed:
+  {
+    GST_DEBUG_OBJECT (src, "subclass refused event");
+    gst_object_unref (src);
+    gst_event_unref (event);
+    return result;
+  }
+not_seekable:
+  {
+    GST_DEBUG_OBJECT (src, "is not seekable");
+    gst_object_unref (src);
+    gst_event_unref (event);
+    return FALSE;
+  }
 }
 
 static void
@@ -772,6 +915,7 @@ gst_base_src_loop (GstPad * pad)
 
   src = GST_BASE_SRC (gst_pad_get_parent (pad));
 
+  /* only send segments when operating in push mode */
   if (G_UNLIKELY (src->need_newsegment)) {
     /* now send newsegment */
     gst_base_src_newsegment (src);
@@ -800,9 +944,10 @@ gst_base_src_loop (GstPad * pad)
   /* special cases */
 eos:
   {
-    GST_DEBUG_OBJECT (src, "going to EOS");
+    GST_DEBUG_OBJECT (src, "going to EOS, getrange returned UNEXPECTED");
     gst_pad_pause_task (pad);
     if (src->segment_loop) {
+      /* FIXME, subclass might want to use another format */
       gst_element_post_message (GST_ELEMENT (src),
           gst_message_new_segment_done (GST_OBJECT (src),
               GST_FORMAT_BYTES, src->segment_end));
@@ -815,9 +960,7 @@ eos:
   }
 pause:
   {
-    const gchar *reason;
-
-    reason = gst_flow_get_name (ret);
+    const gchar *reason = gst_flow_get_name (ret);
 
     GST_DEBUG_OBJECT (src, "pausing task, reason %s", reason);
     gst_pad_pause_task (pad);
@@ -850,7 +993,7 @@ static gboolean
 gst_base_src_unlock (GstBaseSrc * basesrc)
 {
   GstBaseSrcClass *bclass;
-  gboolean result = FALSE;
+  gboolean result = TRUE;
 
   GST_DEBUG ("unlock");
   /* unblock whatever the subclass is doing */
@@ -905,6 +1048,7 @@ gst_base_src_is_seekable (GstBaseSrc * basesrc)
   return basesrc->seekable;
 }
 
+/* default negotiation code */
 static gboolean
 gst_base_src_default_negotiate (GstBaseSrc * basesrc)
 {
@@ -913,38 +1057,48 @@ gst_base_src_default_negotiate (GstBaseSrc * basesrc)
   GstCaps *peercaps = NULL;
   gboolean result = FALSE;
 
+  /* first see what is possible on our source pad */
   thiscaps = gst_pad_get_caps (GST_BASE_SRC_PAD (basesrc));
   GST_DEBUG ("caps of src: %" GST_PTR_FORMAT, thiscaps);
+  /* nothing or anything is allowed, we're done */
   if (thiscaps == NULL || gst_caps_is_any (thiscaps))
     goto no_nego_needed;
 
+  /* get the peer caps */
   peercaps = gst_pad_peer_get_caps (GST_BASE_SRC_PAD (basesrc));
   GST_DEBUG ("caps of peer: %" GST_PTR_FORMAT, peercaps);
   if (peercaps) {
     GstCaps *icaps;
 
+    /* get intersection */
     icaps = gst_caps_intersect (thiscaps, peercaps);
     GST_DEBUG ("intersect: %" GST_PTR_FORMAT, icaps);
     gst_caps_unref (thiscaps);
     gst_caps_unref (peercaps);
     if (icaps) {
+      /* take first (and best) possibility */
       caps = gst_caps_copy_nth (icaps, 0);
       gst_caps_unref (icaps);
     }
   } else {
+    /* no peer, work with our own caps then */
     caps = thiscaps;
   }
   if (caps) {
     caps = gst_caps_make_writable (caps);
     gst_caps_truncate (caps);
 
+    /* now fixate */
     gst_pad_fixate_caps (GST_BASE_SRC_PAD (basesrc), caps);
     GST_DEBUG ("fixated to: %" GST_PTR_FORMAT, caps);
 
     if (gst_caps_is_any (caps)) {
+      /* hmm, still anything, so element can do anything and
+       * nego is not needed */
       gst_caps_unref (caps);
       result = TRUE;
     } else if (gst_caps_is_fixed (caps)) {
+      /* yay, fixed caps, use those then */
       gst_pad_set_caps (GST_BASE_SRC_PAD (basesrc), caps);
       gst_caps_unref (caps);
       result = TRUE;
@@ -999,9 +1153,6 @@ gst_base_src_start (GstBaseSrc * basesrc)
 
   GST_OBJECT_FLAG_SET (basesrc, GST_BASE_SRC_STARTED);
 
-  /* start in the beginning */
-  basesrc->offset = 0;
-
   /* figure out the size */
   if (bclass->get_size) {
     result = bclass->get_size (basesrc, &basesrc->size);
@@ -1014,15 +1165,11 @@ gst_base_src_start (GstBaseSrc * basesrc)
 
   GST_DEBUG ("size %d %lld", result, basesrc->size);
 
-  /* we always run to the end when starting */
-  basesrc->segment_loop = FALSE;
-  basesrc->segment_start = 0;
-  basesrc->segment_end = basesrc->size;
-  basesrc->need_newsegment = TRUE;
-
   /* check if we can seek, updates ->seekable */
   gst_base_src_is_seekable (basesrc);
 
+  basesrc->need_newsegment = TRUE;
+
   /* run typefind */
 #if 0
   if (basesrc->seekable) {
@@ -1088,10 +1235,10 @@ gst_base_src_deactivate (GstBaseSrc * basesrc, GstPad * pad)
   GST_LIVE_UNLOCK (basesrc);
 
   /* step 1, unblock clock sync (if any) */
-  gst_base_src_unlock (basesrc);
+  result = gst_base_src_unlock (basesrc);
 
   /* step 2, make sure streaming finishes */
-  result = gst_pad_stop_task (pad);
+  result &= gst_pad_stop_task (pad);
 
   return result;
 }
@@ -1100,6 +1247,7 @@ static gboolean
 gst_base_src_activate_push (GstPad * pad, gboolean active)
 {
   GstBaseSrc *basesrc;
+  gboolean res;
 
   basesrc = GST_BASE_SRC (GST_OBJECT_PARENT (pad));
 
@@ -1113,12 +1261,14 @@ gst_base_src_activate_push (GstPad * pad, gboolean active)
     if (!gst_base_src_start (basesrc))
       goto error_start;
 
-    return gst_pad_start_task (pad, (GstTaskFunction) gst_base_src_loop, pad);
+    res = gst_pad_start_task (pad, (GstTaskFunction) gst_base_src_loop, pad);
   } else {
     GST_DEBUG_OBJECT (basesrc, "Deactivating in push mode");
-    return gst_base_src_deactivate (basesrc, pad);
+    res = gst_base_src_deactivate (basesrc, pad);
   }
+  return res;
 
+  /* ERRORS */
 no_push_activation:
   {
     GST_DEBUG_OBJECT (basesrc, "Subclass disabled push-mode activation");
@@ -1182,7 +1332,6 @@ gst_base_src_change_state (GstElement * element, GstStateChange transition)
 
   basesrc = GST_BASE_SRC (element);
 
-
   switch (transition) {
     case GST_STATE_CHANGE_NULL_TO_READY:
       break;
@@ -1212,6 +1361,15 @@ gst_base_src_change_state (GstElement * element, GstStateChange transition)
     goto failure;
 
   switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      /* we always run from start to end when in READY, after putting
+       * the element to READY a seek can be done on the element to
+       * configure the segment when going to PAUSED. */
+      basesrc->segment_loop = FALSE;
+      basesrc->segment_start = 0;
+      basesrc->segment_end = -1;
+      basesrc->offset = 0;
+      break;
     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
       GST_LIVE_LOCK (element);
       if (basesrc->is_live) {
@@ -1222,7 +1380,12 @@ gst_base_src_change_state (GstElement * element, GstStateChange transition)
       break;
     case GST_STATE_CHANGE_PAUSED_TO_READY:
       if (!gst_base_src_stop (basesrc))
-        result = GST_STATE_CHANGE_FAILURE;
+        goto error_stop;
+      /* we always run from start to end when in READY */
+      basesrc->segment_loop = FALSE;
+      basesrc->segment_start = 0;
+      basesrc->segment_end = -1;
+      basesrc->offset = 0;
       break;
     case GST_STATE_CHANGE_READY_TO_NULL:
       break;
@@ -1231,14 +1394,20 @@ gst_base_src_change_state (GstElement * element, GstStateChange transition)
   }
 
   if (no_preroll && result == GST_STATE_CHANGE_SUCCESS)
-    return GST_STATE_CHANGE_NO_PREROLL;
-  else
-    return result;
+    result = GST_STATE_CHANGE_NO_PREROLL;
+
+  return result;
 
   /* ERRORS */
 failure:
   {
+    GST_DEBUG_OBJECT (basesrc, "parent failed state change");
     gst_base_src_stop (basesrc);
     return result;
   }
+error_stop:
+  {
+    GST_DEBUG_OBJECT (basesrc, "Failed to stop");
+    return GST_STATE_CHANGE_FAILURE;
+  }
 }
index b86ee46..2d8b0f7 100644 (file)
@@ -113,6 +113,7 @@ static void gst_base_src_set_property (GObject * object, guint prop_id,
 static void gst_base_src_get_property (GObject * object, guint prop_id,
     GValue * value, GParamSpec * pspec);
 static gboolean gst_base_src_event_handler (GstPad * pad, GstEvent * event);
+static gboolean gst_base_src_send_event (GstElement * elem, GstEvent * event);
 
 static gboolean gst_base_src_query (GstPad * pad, GstQuery * query);
 
@@ -167,6 +168,7 @@ gst_base_src_class_init (GstBaseSrcClass * klass)
 
   gstelement_class->change_state =
       GST_DEBUG_FUNCPTR (gst_base_src_change_state);
+  gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_base_src_send_event);
 
   klass->negotiate = gst_base_src_default_negotiate;
   klass->newsegment = gst_base_src_default_newsegment;
@@ -214,10 +216,6 @@ gst_base_src_init (GstBaseSrc * basesrc, gpointer g_class)
   GST_DEBUG_OBJECT (basesrc, "adding src pad");
   gst_element_add_pad (GST_ELEMENT (basesrc), pad);
 
-  basesrc->segment_loop = FALSE;
-  basesrc->segment_start = -1;
-  basesrc->segment_end = -1;
-  basesrc->need_newsegment = TRUE;
   basesrc->blocksize = DEFAULT_BLOCKSIZE;
   basesrc->clock_id = NULL;
 
@@ -392,10 +390,26 @@ gst_base_src_query (GstPad * pad, GstQuery * query)
 
     case GST_QUERY_SEEKING:
       gst_query_set_seeking (query, GST_FORMAT_BYTES,
-          src->seekable, src->segment_start, src->segment_end);
+          src->seekable, 0, src->size);
       res = TRUE;
       break;
 
+    case GST_QUERY_SEGMENT:
+    {
+      gint64 start, stop;
+
+      start = src->segment_start;
+      /* no end segment configured, current size then */
+      if ((stop = src->segment_end) == -1)
+        stop = src->size;
+
+      /* FIXME, we can't report our rate as we did not store it, d'oh!.
+       * Also, subclasses might want to support other formats. */
+      gst_query_set_segment (query, 1.0, GST_FORMAT_BYTES, start, stop);
+      res = TRUE;
+      break;
+    }
+
     case GST_QUERY_FORMATS:
       gst_query_set_formats (query, 3, GST_FORMAT_DEFAULT,
           GST_FORMAT_BYTES, GST_FORMAT_PERCENT);
@@ -421,12 +435,11 @@ gst_base_src_default_newsegment (GstBaseSrc * src)
   GstEvent *event;
 
   GST_DEBUG_OBJECT (src, "Sending newsegment from %" G_GINT64_FORMAT
-      " to %" G_GINT64_FORMAT, (gint64) src->segment_start,
-      (gint64) src->segment_end);
+      " to %" G_GINT64_FORMAT, src->segment_start, src->segment_end);
+
   event = gst_event_new_newsegment (FALSE, 1.0,
-      GST_FORMAT_BYTES,
-      (gint64) src->segment_start, (gint64) src->segment_end,
-      (gint64) src->segment_start);
+      GST_FORMAT_BYTES, src->segment_start, src->segment_end,
+      src->segment_start);
 
   return gst_pad_push_event (src->srcpad, event);
 }
@@ -445,45 +458,31 @@ gst_base_src_newsegment (GstBaseSrc * src)
   return result;
 }
 
+/* based on the event parameters configure the segment_start/stop
+ * times. Called with STREAM_LOCK.
+ */
 static gboolean
-gst_base_src_do_seek (GstBaseSrc * src, GstEvent * event)
+gst_base_src_configure_segment (GstBaseSrc * src, GstEvent * event)
 {
   gdouble rate;
   GstFormat format;
   GstSeekFlags flags;
   GstSeekType cur_type, stop_type;
   gint64 cur, stop;
-  gboolean flush;
-  gboolean update_stop = TRUE, update_start = TRUE;
+  gboolean update_stop, update_start;
 
   gst_event_parse_seek (event, &rate, &format, &flags,
       &cur_type, &cur, &stop_type, &stop);
 
-  /* get seek format */
-  if (format == GST_FORMAT_DEFAULT)
-    format = GST_FORMAT_BYTES;
-  /* we can only seek bytes */
-  if (format != GST_FORMAT_BYTES)
-    goto unsupported_seek;
-
-  flush = flags & GST_SEEK_FLAG_FLUSH;
-
-  /* get seek positions */
+  /* parse the loop flag */
+  /* FIXME, need to store other flags and rate too */
   src->segment_loop = (flags & GST_SEEK_FLAG_SEGMENT) != 0;
 
-  /* send flush start */
-  if (flush)
-    gst_pad_push_event (src->srcpad, gst_event_new_flush_start ());
-  else
-    gst_pad_pause_task (src->srcpad);
+  /* assume we'll update both start and stop values */
+  update_start = TRUE;
+  update_stop = TRUE;
 
-  /* unblock streaming thread */
-  gst_base_src_unlock (src);
-
-  /* grab streaming lock */
-  GST_STREAM_LOCK (src->srcpad);
-
-  /* perform the seek */
+  /* perform the seek, segment_start is never invalid */
   switch (cur_type) {
     case GST_SEEK_TYPE_NONE:
       /* no update to segment */
@@ -491,7 +490,7 @@ gst_base_src_do_seek (GstBaseSrc * src, GstEvent * event)
       update_start = FALSE;
       break;
     case GST_SEEK_TYPE_SET:
-      /* cur hold desired position */
+      /* cur holds desired position */
       break;
     case GST_SEEK_TYPE_CUR:
       /* add cur to currently configure segment */
@@ -503,64 +502,187 @@ gst_base_src_do_seek (GstBaseSrc * src, GstEvent * event)
       break;
   }
   /* bring in sane range */
-  cur = CLAMP (cur, 0, src->size);
+  if (src->size != -1)
+    cur = CLAMP (cur, 0, src->size);
+  else
+    cur = MAX (cur, 0);
 
+  /* segment_end can be -1 if we have not configured a stop. */
   switch (stop_type) {
     case GST_SEEK_TYPE_NONE:
       stop = src->segment_end;
       update_stop = FALSE;
       break;
     case GST_SEEK_TYPE_SET:
+      /* stop folds required value */
       break;
     case GST_SEEK_TYPE_CUR:
-      stop = src->segment_end + stop;
+      if (src->segment_end != -1)
+        stop = src->segment_end + stop;
+      else
+        stop = -1;
       break;
     case GST_SEEK_TYPE_END:
-      stop = src->size + stop;
+      if (src->size != -1)
+        stop = src->size + stop;
+      else
+        stop = -1;
       break;
   }
-  stop = CLAMP (stop, 0, src->size);
+
+  /* if we have a valid stop time, make sure it is clipped */
+  if (stop != -1) {
+    if (src->size != -1)
+      stop = CLAMP (stop, 0, src->size);
+    else
+      stop = MAX (stop, 0);
+  }
 
   src->segment_start = cur;
   src->segment_end = stop;
 
-  /* update our offset */
-  src->offset = cur;
+  /* update our offset if it was updated */
+  if (update_start)
+    src->offset = cur;
 
-  GST_DEBUG_OBJECT (src, "seek pending for segment from %" G_GINT64_FORMAT
+  GST_DEBUG_OBJECT (src, "segment configured from %" G_GINT64_FORMAT
       " to %" G_GINT64_FORMAT, src->segment_start, src->segment_end);
 
-  /* now make sure the newsegment will be send */
-  src->need_newsegment = TRUE;
+  return TRUE;
+}
+
+/* this code implements the seeking. It is a good example
+ * handling all cases (modulo the FIXMEs).
+ *
+ * A seek updates the currently configured segment_start
+ * and segment_stop values based on the SEEK_TYPE. If the
+ * segment_start value is updated, a seek to this new position
+ * should be performed.
+ *
+ * The seek can only be executed when we are not currently
+ * streaming any data, to make sure that this is the case, we
+ * acquire the STREAM_LOCK which is taken when we are in the
+ * _loop() function or when a getrange() is called. Normally
+ * we will not receive a seek if we are operating in pull mode
+ * though.
+ *
+ * When we are in the loop() function, we might be in the middle
+ * of pushing a buffer, which might block in a sink. To make sure
+ * that the push gets unblocked we push out a FLUSH_START event.
+ * Our loop function will get a WRONG_STATE return value from
+ * the push and will pause, effectively releasing the STREAM_LOCK.
+ *
+ * For a non-flushing seek, we pause the task, which might eventually
+ * release the STREAM_LOCK. We say eventually because when the sink
+ * blocks on the sample we might wait a very long time until the sink
+ * unblocks the sample. In any case we acquire the STREAM_LOCK and
+ * can continue the seek. A non-flushing seek is normally done in a 
+ * running pipeline to perform seamless playback.
+ *
+ * After updating the segment_start/stop values, we prepare for
+ * streaming again. We push out a FLUSH_STOP to make the peer pad
+ * accept data again and we start our task again.
+ *
+ * A segment seek posts a message on the bus saying that the playback
+ * of the segment started. We store the segment flag internally because
+ * when we reach the segment_stop we have to post a segment_done
+ * instead of EOS when doing a segment seek.
+ */
+static gboolean
+gst_base_src_do_seek (GstBaseSrc * src, GstEvent * event)
+{
+  gdouble rate;
+  GstFormat format;
+  GstSeekFlags flags;
+  gboolean flush;
+
+  gst_event_parse_seek (event, &rate, &format, &flags, NULL, NULL, NULL, NULL);
+
+  /* FIXME subclasses should be able to provide other formats */
+  /* get seek format */
+  if (format == GST_FORMAT_DEFAULT)
+    format = GST_FORMAT_BYTES;
+  /* we can only seek bytes */
+  if (format != GST_FORMAT_BYTES)
+    goto unsupported_seek;
+
+  flush = flags & GST_SEEK_FLAG_FLUSH;
+
+  /* send flush start */
+  if (flush)
+    gst_pad_push_event (src->srcpad, gst_event_new_flush_start ());
+  else
+    gst_pad_pause_task (src->srcpad);
 
+  /* unblock streaming thread */
+  gst_base_src_unlock (src);
+
+  /* grab streaming lock, this should eventually be possible, either
+   * because the task is paused or out streaming thread stopped 
+   * because our peer is flushing. */
+  GST_STREAM_LOCK (src->srcpad);
+
+  /* now configure the segment */
+  gst_base_src_configure_segment (src, event);
+
+  /* and prepare to continue streaming */
   if (flush)
-    /* send flush stop */
+    /* send flush stop, peer will accept data and events again. We
+     * are not yet providing data as we still have the STREAM_LOCK. */
     gst_pad_push_event (src->srcpad, gst_event_new_flush_stop ());
 
+  /* now make sure the newsegment will be send from the streaming
+   * thread. We could opt to send it here too. */
+  src->need_newsegment = TRUE;
+
   if (src->segment_loop) {
+    /* FIXME subclasses should be able to provide other formats */
     gst_element_post_message (GST_ELEMENT (src),
         gst_message_new_segment_start (GST_OBJECT (src), GST_FORMAT_BYTES,
-            cur));
+            src->segment_start));
   }
 
-  /* and restart the task */
+  /* and restart the task in case it got paused explicitely or by
+   * the FLUSH_START event we pushed out. */
   gst_pad_start_task (src->srcpad, (GstTaskFunction) gst_base_src_loop,
       src->srcpad);
 
+  /* and release the lock again so we can continue streaming */
   GST_STREAM_UNLOCK (src->srcpad);
 
-  gst_event_unref (event);
-
   return TRUE;
 
   /* ERROR */
 unsupported_seek:
   {
     GST_DEBUG_OBJECT (src, "invalid format, seek aborted.");
+
     return FALSE;
   }
 }
 
+/* all events send to this element directly
+ */
+static gboolean
+gst_base_src_send_event (GstElement * element, GstEvent * event)
+{
+  GstBaseSrc *src;
+  gboolean result;
+
+  src = GST_BASE_SRC (element);
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_SEEK:
+      result = gst_base_src_configure_segment (src, event);
+      break;
+    default:
+      result = FALSE;
+      break;
+  }
+
+  return result;
+}
+
 static gboolean
 gst_base_src_event_handler (GstPad * pad, GstEvent * event)
 {
@@ -568,31 +690,52 @@ gst_base_src_event_handler (GstPad * pad, GstEvent * event)
   GstBaseSrcClass *bclass;
   gboolean result;
 
-  src = GST_BASE_SRC (GST_PAD_PARENT (pad));
+  src = GST_BASE_SRC (gst_pad_get_parent (pad));
   bclass = GST_BASE_SRC_GET_CLASS (src);
 
-  if (bclass->event)
-    result = bclass->event (src, event);
+  if (bclass->event) {
+    if (!(result = bclass->event (src, event)))
+      goto subclass_failed;
+  }
 
   switch (GST_EVENT_TYPE (event)) {
     case GST_EVENT_SEEK:
-      if (!src->seekable) {
-        gst_event_unref (event);
-        return FALSE;
-      }
-      return gst_base_src_do_seek (src, event);
+      /* is normally called when in push mode */
+      if (!src->seekable)
+        goto not_seekable;
+
+      result = gst_base_src_do_seek (src, event);
+      break;
     case GST_EVENT_FLUSH_START:
-      /* cancel any blocking getrange */
-      gst_base_src_unlock (src);
+      /* cancel any blocking getrange, is normally called
+       * when in pull mode. */
+      result = gst_base_src_unlock (src);
       break;
     case GST_EVENT_FLUSH_STOP:
-      break;
     default:
+      result = TRUE;
       break;
   }
   gst_event_unref (event);
+  gst_object_unref (src);
 
-  return TRUE;
+  return result;
+
+  /* ERRORS */
+subclass_failed:
+  {
+    GST_DEBUG_OBJECT (src, "subclass refused event");
+    gst_object_unref (src);
+    gst_event_unref (event);
+    return result;
+  }
+not_seekable:
+  {
+    GST_DEBUG_OBJECT (src, "is not seekable");
+    gst_object_unref (src);
+    gst_event_unref (event);
+    return FALSE;
+  }
 }
 
 static void
@@ -772,6 +915,7 @@ gst_base_src_loop (GstPad * pad)
 
   src = GST_BASE_SRC (gst_pad_get_parent (pad));
 
+  /* only send segments when operating in push mode */
   if (G_UNLIKELY (src->need_newsegment)) {
     /* now send newsegment */
     gst_base_src_newsegment (src);
@@ -800,9 +944,10 @@ gst_base_src_loop (GstPad * pad)
   /* special cases */
 eos:
   {
-    GST_DEBUG_OBJECT (src, "going to EOS");
+    GST_DEBUG_OBJECT (src, "going to EOS, getrange returned UNEXPECTED");
     gst_pad_pause_task (pad);
     if (src->segment_loop) {
+      /* FIXME, subclass might want to use another format */
       gst_element_post_message (GST_ELEMENT (src),
           gst_message_new_segment_done (GST_OBJECT (src),
               GST_FORMAT_BYTES, src->segment_end));
@@ -815,9 +960,7 @@ eos:
   }
 pause:
   {
-    const gchar *reason;
-
-    reason = gst_flow_get_name (ret);
+    const gchar *reason = gst_flow_get_name (ret);
 
     GST_DEBUG_OBJECT (src, "pausing task, reason %s", reason);
     gst_pad_pause_task (pad);
@@ -850,7 +993,7 @@ static gboolean
 gst_base_src_unlock (GstBaseSrc * basesrc)
 {
   GstBaseSrcClass *bclass;
-  gboolean result = FALSE;
+  gboolean result = TRUE;
 
   GST_DEBUG ("unlock");
   /* unblock whatever the subclass is doing */
@@ -905,6 +1048,7 @@ gst_base_src_is_seekable (GstBaseSrc * basesrc)
   return basesrc->seekable;
 }
 
+/* default negotiation code */
 static gboolean
 gst_base_src_default_negotiate (GstBaseSrc * basesrc)
 {
@@ -913,38 +1057,48 @@ gst_base_src_default_negotiate (GstBaseSrc * basesrc)
   GstCaps *peercaps = NULL;
   gboolean result = FALSE;
 
+  /* first see what is possible on our source pad */
   thiscaps = gst_pad_get_caps (GST_BASE_SRC_PAD (basesrc));
   GST_DEBUG ("caps of src: %" GST_PTR_FORMAT, thiscaps);
+  /* nothing or anything is allowed, we're done */
   if (thiscaps == NULL || gst_caps_is_any (thiscaps))
     goto no_nego_needed;
 
+  /* get the peer caps */
   peercaps = gst_pad_peer_get_caps (GST_BASE_SRC_PAD (basesrc));
   GST_DEBUG ("caps of peer: %" GST_PTR_FORMAT, peercaps);
   if (peercaps) {
     GstCaps *icaps;
 
+    /* get intersection */
     icaps = gst_caps_intersect (thiscaps, peercaps);
     GST_DEBUG ("intersect: %" GST_PTR_FORMAT, icaps);
     gst_caps_unref (thiscaps);
     gst_caps_unref (peercaps);
     if (icaps) {
+      /* take first (and best) possibility */
       caps = gst_caps_copy_nth (icaps, 0);
       gst_caps_unref (icaps);
     }
   } else {
+    /* no peer, work with our own caps then */
     caps = thiscaps;
   }
   if (caps) {
     caps = gst_caps_make_writable (caps);
     gst_caps_truncate (caps);
 
+    /* now fixate */
     gst_pad_fixate_caps (GST_BASE_SRC_PAD (basesrc), caps);
     GST_DEBUG ("fixated to: %" GST_PTR_FORMAT, caps);
 
     if (gst_caps_is_any (caps)) {
+      /* hmm, still anything, so element can do anything and
+       * nego is not needed */
       gst_caps_unref (caps);
       result = TRUE;
     } else if (gst_caps_is_fixed (caps)) {
+      /* yay, fixed caps, use those then */
       gst_pad_set_caps (GST_BASE_SRC_PAD (basesrc), caps);
       gst_caps_unref (caps);
       result = TRUE;
@@ -999,9 +1153,6 @@ gst_base_src_start (GstBaseSrc * basesrc)
 
   GST_OBJECT_FLAG_SET (basesrc, GST_BASE_SRC_STARTED);
 
-  /* start in the beginning */
-  basesrc->offset = 0;
-
   /* figure out the size */
   if (bclass->get_size) {
     result = bclass->get_size (basesrc, &basesrc->size);
@@ -1014,15 +1165,11 @@ gst_base_src_start (GstBaseSrc * basesrc)
 
   GST_DEBUG ("size %d %lld", result, basesrc->size);
 
-  /* we always run to the end when starting */
-  basesrc->segment_loop = FALSE;
-  basesrc->segment_start = 0;
-  basesrc->segment_end = basesrc->size;
-  basesrc->need_newsegment = TRUE;
-
   /* check if we can seek, updates ->seekable */
   gst_base_src_is_seekable (basesrc);
 
+  basesrc->need_newsegment = TRUE;
+
   /* run typefind */
 #if 0
   if (basesrc->seekable) {
@@ -1088,10 +1235,10 @@ gst_base_src_deactivate (GstBaseSrc * basesrc, GstPad * pad)
   GST_LIVE_UNLOCK (basesrc);
 
   /* step 1, unblock clock sync (if any) */
-  gst_base_src_unlock (basesrc);
+  result = gst_base_src_unlock (basesrc);
 
   /* step 2, make sure streaming finishes */
-  result = gst_pad_stop_task (pad);
+  result &= gst_pad_stop_task (pad);
 
   return result;
 }
@@ -1100,6 +1247,7 @@ static gboolean
 gst_base_src_activate_push (GstPad * pad, gboolean active)
 {
   GstBaseSrc *basesrc;
+  gboolean res;
 
   basesrc = GST_BASE_SRC (GST_OBJECT_PARENT (pad));
 
@@ -1113,12 +1261,14 @@ gst_base_src_activate_push (GstPad * pad, gboolean active)
     if (!gst_base_src_start (basesrc))
       goto error_start;
 
-    return gst_pad_start_task (pad, (GstTaskFunction) gst_base_src_loop, pad);
+    res = gst_pad_start_task (pad, (GstTaskFunction) gst_base_src_loop, pad);
   } else {
     GST_DEBUG_OBJECT (basesrc, "Deactivating in push mode");
-    return gst_base_src_deactivate (basesrc, pad);
+    res = gst_base_src_deactivate (basesrc, pad);
   }
+  return res;
 
+  /* ERRORS */
 no_push_activation:
   {
     GST_DEBUG_OBJECT (basesrc, "Subclass disabled push-mode activation");
@@ -1182,7 +1332,6 @@ gst_base_src_change_state (GstElement * element, GstStateChange transition)
 
   basesrc = GST_BASE_SRC (element);
 
-
   switch (transition) {
     case GST_STATE_CHANGE_NULL_TO_READY:
       break;
@@ -1212,6 +1361,15 @@ gst_base_src_change_state (GstElement * element, GstStateChange transition)
     goto failure;
 
   switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      /* we always run from start to end when in READY, after putting
+       * the element to READY a seek can be done on the element to
+       * configure the segment when going to PAUSED. */
+      basesrc->segment_loop = FALSE;
+      basesrc->segment_start = 0;
+      basesrc->segment_end = -1;
+      basesrc->offset = 0;
+      break;
     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
       GST_LIVE_LOCK (element);
       if (basesrc->is_live) {
@@ -1222,7 +1380,12 @@ gst_base_src_change_state (GstElement * element, GstStateChange transition)
       break;
     case GST_STATE_CHANGE_PAUSED_TO_READY:
       if (!gst_base_src_stop (basesrc))
-        result = GST_STATE_CHANGE_FAILURE;
+        goto error_stop;
+      /* we always run from start to end when in READY */
+      basesrc->segment_loop = FALSE;
+      basesrc->segment_start = 0;
+      basesrc->segment_end = -1;
+      basesrc->offset = 0;
       break;
     case GST_STATE_CHANGE_READY_TO_NULL:
       break;
@@ -1231,14 +1394,20 @@ gst_base_src_change_state (GstElement * element, GstStateChange transition)
   }
 
   if (no_preroll && result == GST_STATE_CHANGE_SUCCESS)
-    return GST_STATE_CHANGE_NO_PREROLL;
-  else
-    return result;
+    result = GST_STATE_CHANGE_NO_PREROLL;
+
+  return result;
 
   /* ERRORS */
 failure:
   {
+    GST_DEBUG_OBJECT (basesrc, "parent failed state change");
     gst_base_src_stop (basesrc);
     return result;
   }
+error_stop:
+  {
+    GST_DEBUG_OBJECT (basesrc, "Failed to stop");
+    return GST_STATE_CHANGE_FAILURE;
+  }
 }