ext/theora/: Document and partially implement an algorithm for doing reverse playback...
authorWim Taymans <wim.taymans@gmail.com>
Thu, 2 Nov 2006 15:06:36 +0000 (15:06 +0000)
committerWim Taymans <wim.taymans@gmail.com>
Thu, 2 Nov 2006 15:06:36 +0000 (15:06 +0000)
Original commit message from CVS:
* ext/theora/gsttheoradec.h:
* ext/theora/theoradec.c: (gst_theora_dec_init),
(theora_dec_sink_event), (theora_dec_chain_forward),
(theora_dec_flush_decode), (theora_dec_chain_reverse),
(theora_dec_chain):
Document and partially implement an algorithm for doing reverse playback
of theora video.

ChangeLog
ext/theora/gsttheoradec.h
ext/theora/theoradec.c

index 4d31ee5..2fe7bd9 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2006-11-02  Wim Taymans  <wim@fluendo.com>
+
+       * ext/theora/gsttheoradec.h:
+       * ext/theora/theoradec.c: (gst_theora_dec_init),
+       (theora_dec_sink_event), (theora_dec_chain_forward),
+       (theora_dec_flush_decode), (theora_dec_chain_reverse),
+       (theora_dec_chain):
+       Document and partially implement an algorithm for doing reverse playback
+       of theora video.
+
 2006-11-02  Tim-Philipp Müller  <tim at centricular dot net>
 
        Patch by: Sergey Scobich  <sergey.scobich at gmail com>
index bf12b69..a0d5de9 100644 (file)
@@ -74,7 +74,13 @@ struct _GstTheoraDec
 
   gboolean crop;
 
+  /* list of buffers that need timestamps */
   GList *queued;
+  /* list of raw output buffers */
+  GList *output;
+  /* gather/decode queues for reverse playback */
+  GList *gather;
+  GList *decode;
 
   /* segment info */ /* with STREAM_LOCK */
   GstSegment segment;
index a233579..72f6913 100644 (file)
@@ -163,6 +163,8 @@ gst_theora_dec_init (GstTheoraDec * dec, GstTheoraDecClass * g_class)
   gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad);
 
   dec->crop = THEORA_DEF_CROP;
+  dec->gather = NULL;
+  dec->decode = NULL;
   dec->queued = NULL;
 }
 
@@ -681,9 +683,6 @@ theora_dec_sink_event (GstPad * pad, GstEvent * event)
       if (format != GST_FORMAT_TIME)
         goto newseg_wrong_format;
 
-      if (rate <= 0.0)
-        goto newseg_wrong_rate;
-
       /* now configure the values */
       gst_segment_set_newsegment_full (&dec->segment, update,
           rate, arate, format, start, stop, time);
@@ -708,12 +707,6 @@ newseg_wrong_format:
     gst_event_unref (event);
     goto done;
   }
-newseg_wrong_rate:
-  {
-    GST_DEBUG_OBJECT (dec, "negative rates not supported yet");
-    gst_event_unref (event);
-    goto done;
-  }
 }
 
 static GstFlowReturn
@@ -1150,16 +1143,13 @@ no_buffer:
 }
 
 static GstFlowReturn
-theora_dec_chain (GstPad * pad, GstBuffer * buf)
+theora_dec_chain_forward (GstTheoraDec * dec, gboolean discont, GstBuffer * buf)
 {
-  GstTheoraDec *dec;
   ogg_packet packet;
   GstFlowReturn result = GST_FLOW_OK;
 
-  dec = GST_THEORA_DEC (gst_pad_get_parent (pad));
-
   /* resync on DISCONT */
-  if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT))) {
+  if (G_UNLIKELY (discont)) {
     GST_DEBUG_OBJECT (dec, "received DISCONT buffer");
     dec->need_keyframe = TRUE;
     dec->last_timestamp = -1;
@@ -1209,13 +1199,173 @@ done:
   /* interpolate granule pos */
   dec->granulepos = _inc_granulepos (dec, dec->granulepos);
 
-  gst_object_unref (dec);
-
   gst_buffer_unref (buf);
 
   return result;
 }
 
+/* For reverse playback we use a technique that can be used for
+ * any keyframe based video codec.
+ *
+ * Input:
+ *  Buffer decoding order:  7  8  9  4  5  6  1  2  3  EOS
+ *  Keyframe flag:                      K        K
+ *  Discont flag:           D        D        D
+ *
+ * - Each Discont marks a discont in the decoding order.
+ * - The keyframes mark where we can start decoding.
+ *
+ * First we prepend incomming buffers to the gather queue, whenever we receive
+ * a discont, we flush out the gather queue.
+ *
+ * The above data will be accumulated in the gather queue like this:
+ *
+ *   gather queue:  9  8  7
+ *                        D
+ *
+ * Whe buffer 4 is received (with a DISCONT), we flush the gather queue like
+ * this:
+ *
+ *   while (gather)
+ *     take head of queue and prepend to decode queue.
+ *     if we copied a keyframe, decode the decode queue.
+ *
+ * After we flushed the gather queue, we add 4 to the (now empty) gather queue.
+ * We get the following situation:
+ *
+ *  gather queue:    4
+ *  decode queue:    7  8  9
+ *
+ * After we received 5 (Keyframe) and 6:
+ *
+ *  gather queue:    6  5  4
+ *  decode queue:    7  8  9
+ *
+ * When we receive 1 (DISCONT) which triggers a flush of the gather queue:
+ *
+ *   Copy head of the gather queue (6) to decode queue:
+ *
+ *    gather queue:    5  4
+ *    decode queue:    6  7  8  9
+ *
+ *   Copy head of the gather queue (5) to decode queue. This is a keyframe so we
+ *   can start decoding.
+ *
+ *    gather queue:    4
+ *    decode queue:    5  6  7  8  9
+ *
+ *   Decode frames in decode queue, store raw decoded data in output queue, we
+ *   can take the head of the decode queue and prepend the decoded result in the
+ *   output queue:
+ *
+ *    gather queue:    4
+ *    decode queue:    
+ *    output queue:    9  8  7  6  5
+ *
+ *   Now output all the frames in the output queue, picking a frame from the
+ *   head of the queue.
+ *
+ *   Copy head of the gather queue (4) to decode queue, we flushed the gather
+ *   queue an can now store input buffer in the gather queue:
+ *
+ *    gather queue:    1
+ *    decode queue:    4
+ *
+ *  When we receive EOS, the queue looks like:
+ *
+ *    gather queue:    3  2  1
+ *    decode queue:    4
+ *
+ *  Fill decode queue, first keyframe we copy is 2:
+ *
+ *    gather queue:    1
+ *    decode queue:    2  3  4
+ *
+ *  Decoded output:
+ *
+ *    gather queue:    1
+ *    decode queue:    
+ *    output queue:    4  3  2
+ *
+ *  Leftover buffer 1 cannot be decoded and must be discarded.
+ */
+static GstFlowReturn
+theora_dec_flush_decode (GstTheoraDec * dec)
+{
+  GstFlowReturn res = GST_FLOW_OK;
+
+  while (dec->decode) {
+    GstBuffer *buf = GST_BUFFER_CAST (dec->decode->data);
+
+    /* FIXME, decode buffer, prepend to output queue */
+    gst_buffer_unref (buf);
+
+    dec->decode = g_list_delete_link (dec->decode, dec->decode);
+  }
+  while (dec->queued) {
+    GstBuffer *buf = GST_BUFFER_CAST (dec->queued->data);
+
+    /* iterate ouput queue an push downstream */
+    res = gst_pad_push (dec->srcpad, buf);
+
+    dec->queued = g_list_delete_link (dec->queued, dec->queued);
+  }
+
+  return res;
+}
+
+static GstFlowReturn
+theora_dec_chain_reverse (GstTheoraDec * dec, gboolean discont, GstBuffer * buf)
+{
+  GstFlowReturn res = GST_FLOW_OK;
+
+  /* if we have a discont, move buffers to the decode list */
+  if (discont) {
+    while (dec->gather) {
+      GstBuffer *buf;
+      guint8 *data;
+
+      buf = GST_BUFFER_CAST (dec->gather->data);
+      /* remove from the gather list */
+      dec->gather = g_list_delete_link (dec->gather, dec->gather);
+      /* copy to decode queue */
+      dec->decode = g_list_prepend (dec->decode, buf);
+
+      /* if we copied a keyframe, flush and decode the decode queue */
+      data = GST_BUFFER_DATA (buf);
+      if ((data[0] & 0x40) == 0)
+        res = theora_dec_flush_decode (dec);
+    }
+  }
+
+  /* add buffer to gather queue */
+  dec->gather = g_list_prepend (dec->gather, buf);
+
+  return res;
+}
+
+static GstFlowReturn
+theora_dec_chain (GstPad * pad, GstBuffer * buf)
+{
+  GstTheoraDec *dec;
+  GstFlowReturn res;
+  gboolean discont;
+
+  dec = GST_THEORA_DEC (gst_pad_get_parent (pad));
+
+  /* peel of DISCONT flag */
+  discont = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT);
+
+  if (dec->segment.rate > 0.0)
+    res = theora_dec_chain_forward (dec, discont, buf);
+  else
+    res = theora_dec_chain_reverse (dec, discont, buf);
+
+  gst_object_unref (dec);
+
+  return res;
+}
+
 static GstStateChangeReturn
 theora_dec_change_state (GstElement * element, GstStateChange transition)
 {