diff --git a/ChangeLog b/ChangeLog index 4d31ee5af4..2fe7bd952e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2006-11-02 Wim Taymans + + * 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 Patch by: Sergey Scobich diff --git a/ext/theora/gsttheoradec.h b/ext/theora/gsttheoradec.h index bf12b69187..a0d5de9e25 100644 --- a/ext/theora/gsttheoradec.h +++ b/ext/theora/gsttheoradec.h @@ -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; diff --git a/ext/theora/theoradec.c b/ext/theora/theoradec.c index a233579115..72f6913edf 100644 --- a/ext/theora/theoradec.c +++ b/ext/theora/theoradec.c @@ -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) {