commit 82a2a20a39ce01f4c59c135412969c9035880ce9
Author: Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
Date:   Thu Jul 2 15:07:07 2009 +0200

    avidemux: implement tagreader interface.
    
    TAGS_ONLY mode now also tries to activate in pull mode.
    If header provided durations look sane (according to some heuristic),
    then index parsing is skipped altogether, and data chunks are pulled
    sequentially.  Otherwise, index is still parsed to obtain stream
    durations, but only a limited index up to 10 seconds is actually
    built and subsequently used to push data downstream.

diff --git a/gst/avi/Makefile.am b/gst/avi/Makefile.am
index b73f8d5..40a9ff3 100644
--- a/gst/avi/Makefile.am
+++ b/gst/avi/Makefile.am
@@ -18,6 +18,7 @@ libgstavi_la_LIBADD = \
 	$(GST_PLUGINS_BASE_LIBS) \
 	$(GST_BASE_LIBS) \
 	$(GST_LIBS) \
+	-lgsttag-@GST_MAJORMINOR@ \
 	-lgstriff-@GST_MAJORMINOR@
 libgstavi_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
 libgstavi_la_LIBTOOLFLAGS = --tag=disable-static
diff --git a/gst/avi/gstavidemux.c b/gst/avi/gstavidemux.c
index 345e42a..b7a3451 100644
--- a/gst/avi/gstavidemux.c
+++ b/gst/avi/gstavidemux.c
@@ -121,6 +121,8 @@ gst_avi_demux_get_type (void)
     avi_demux_type =
         g_type_register_static (GST_TYPE_ELEMENT,
         "GstAviDemux", &avi_demux_info, 0);
+
+    gst_tag_reader_add_interface_to_type (avi_demux_type);
   }
 
   return avi_demux_type;
@@ -1021,6 +1023,7 @@ gst_avi_demux_parse_subindex (GstAviDemux * avi,
   guint16 bpe;
   guint32 num, i;
   guint64 baseoff;
+  gint64 next_ts;
   gst_avi_index_entry *entries, *entry;
   GList *entries_list = NULL;
   GstFormat format = GST_FORMAT_TIME;
@@ -1060,9 +1063,14 @@ gst_avi_demux_parse_subindex (GstAviDemux * avi,
   if (!(entries = g_try_new (gst_avi_index_entry, num)))
     goto out_of_mem;
 
+  /* start at 0 or where we stopped last */
+  next_ts = (stream->idx_duration == -1) ? 0 : stream->idx_duration;
   for (i = 0; i < num; i++) {
-    gint64 next_ts;
+    gboolean add;
 
+#define GST_TAG_READING_FAST_MIN_DURATION    10 * GST_SECOND
+    add = (next_ts < GST_TAG_READING_FAST_MIN_DURATION) ||
+        (avi->tag_mode != GST_TAG_READING_MODE_TAGS_ONLY);
     entry = &entries[i];
 
     if (size < 24 + bpe * (i + 1))
@@ -1074,35 +1082,25 @@ gst_avi_demux_parse_subindex (GstAviDemux * avi,
     entry->flags =
         (entry->size & 0x80000000) ? 0 : GST_AVI_INDEX_ENTRY_FLAG_KEYFRAME;
     entry->size &= ~0x80000000;
-    entry->index_nr = i;
-    entry->stream_nr = stream->num;
-
-    /* stream duration unknown, now we can calculate it */
-    if (stream->idx_duration == -1)
-      stream->idx_duration = 0;
 
     /* timestamps */
-    entry->ts = stream->idx_duration;
-    if (stream->is_vbr) {
-      /* VBR stream next timestamp */
-      if (stream->strh->type == GST_RIFF_FCC_auds) {
-        gst_avi_demux_src_convert (stream->pad, GST_FORMAT_DEFAULT,
-            stream->total_blocks + 1, &format, &next_ts);
+    if (G_LIKELY (add || (i == (num - 1)))) {
+      entry->ts = next_ts;
+      if (stream->is_vbr) {
+        /* VBR stream next timestamp */
+        if (stream->strh->type == GST_RIFF_FCC_auds) {
+          gst_avi_demux_src_convert (stream->pad, GST_FORMAT_DEFAULT,
+              stream->total_blocks + 1, &format, &next_ts);
+        } else {
+          gst_avi_demux_src_convert (stream->pad, GST_FORMAT_DEFAULT,
+              stream->total_frames + 1, &format, &next_ts);
+        }
       } else {
-        gst_avi_demux_src_convert (stream->pad, GST_FORMAT_DEFAULT,
-            stream->total_frames + 1, &format, &next_ts);
+        /* CBR get next timestamp */
+        gst_avi_demux_src_convert (stream->pad, GST_FORMAT_BYTES,
+            stream->total_bytes + entry->size, &format, &next_ts);
       }
-    } else {
-      /* CBR get next timestamp */
-      gst_avi_demux_src_convert (stream->pad, GST_FORMAT_BYTES,
-          stream->total_bytes + entry->size, &format, &next_ts);
     }
-    /* duration is next - current */
-    entry->dur = next_ts - entry->ts;
-
-    /* stream position */
-    entry->bytes_before = stream->total_bytes;
-    entry->frames_before = stream->total_frames;
 
     stream->total_bytes += entry->size;
     stream->total_frames++;
@@ -1114,16 +1112,32 @@ gst_avi_demux_parse_subindex (GstAviDemux * avi,
       else
         stream->total_blocks++;
     }
-    stream->idx_duration = next_ts;
 
-    entries_list = g_list_prepend (entries_list, entry);
+    /* only pick up duration metadata and minimal data */
+    if (G_LIKELY (add)) {
+      /* fill remaining bits */
+      entry->index_nr = i;
+      entry->stream_nr = stream->num;
+
+      /* duration is next - current */
+      entry->dur = next_ts - entry->ts;
+
+      /* stream position */
+      entry->bytes_before = stream->total_bytes;
+      entry->frames_before = stream->total_frames;
+      entries_list = g_list_prepend (entries_list, entry);
+    }
   }
 
+  /* if any entries, keep last calculated time */
+  if (i)
+    stream->idx_duration = next_ts;
+
   GST_LOG_OBJECT (avi, "Read %d index entries", i);
 
   gst_buffer_unref (buf);
 
-  if (i > 0) {
+  if (entries_list) {
     *_entries_list = g_list_reverse (entries_list);
   } else {
     g_free (entries);
@@ -1417,10 +1431,18 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf)
 
   GST_DEBUG_OBJECT (avi, "Parsing stream");
 
+  /* initial settings */
+  stream->idx_duration = GST_CLOCK_TIME_NONE;
+  stream->hdr_duration = GST_CLOCK_TIME_NONE;
+  stream->duration = GST_CLOCK_TIME_NONE;
+
   while (gst_riff_parse_chunk (element, buf, &offset, &tag, &sub)) {
     /* sub can be NULL if the chunk is empty */
     switch (tag) {
       case GST_RIFF_TAG_strh:
+      {
+        gst_riff_strh *strh;
+
         if (got_strh) {
           GST_WARNING_OBJECT (avi, "Ignoring additional strh chunk");
           break;
@@ -1429,8 +1451,18 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf)
           GST_WARNING_OBJECT (avi, "Failed to parse strh chunk");
           goto fail;
         }
+        /* determine duration as indicated by header */
+        strh = stream->strh;
+        stream->hdr_duration = gst_util_uint64_scale ((guint64) strh->length *
+            strh->scale, GST_SECOND, (guint64) strh->rate);
+        GST_INFO ("Stream duration according to header: %" GST_TIME_FORMAT,
+            GST_TIME_ARGS (stream->hdr_duration));
+        if (stream->hdr_duration == 0)
+          stream->hdr_duration = GST_CLOCK_TIME_NONE;
+
         got_strh = TRUE;
         break;
+      }
       case GST_RIFF_TAG_strf:
       {
         gboolean res = FALSE;
@@ -1651,9 +1683,6 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf)
   pad = stream->pad = gst_pad_new_from_template (templ, padname);
   stream->last_flow = GST_FLOW_OK;
   stream->discont = TRUE;
-  stream->idx_duration = GST_CLOCK_TIME_NONE;
-  stream->hdr_duration = GST_CLOCK_TIME_NONE;
-  stream->duration = GST_CLOCK_TIME_NONE;
   g_free (padname);
 
   gst_pad_use_fixed_caps (pad);
@@ -1948,7 +1977,11 @@ gst_avi_demux_parse_index (GstAviDemux * avi,
         target->index_nr, stream->total_frames - 1, target->flags,
         target->stream_nr, target->size, target->offset,
         GST_TIME_ARGS (target->ts), GST_TIME_ARGS (target->dur));
-    entries_list = g_list_prepend (entries_list, target);
+
+    /* only pick up duration metadata and minimal data */
+    if (G_UNLIKELY (stream->idx_duration < GST_TAG_READING_FAST_MIN_DURATION ||
+            avi->tag_mode != GST_TAG_READING_MODE_TAGS_ONLY))
+      entries_list = g_list_prepend (entries_list, target);
 
     n++;
   }
@@ -1960,7 +1993,7 @@ gst_avi_demux_parse_index (GstAviDemux * avi,
 
   gst_buffer_unref (buf);
 
-  if (n > 0) {
+  if (entries_list) {
     *_entries_list = g_list_reverse (entries_list);
   } else {
     g_free (entries);
@@ -2292,7 +2325,7 @@ gst_avi_demux_next_data_buffer (GstAviDemux * avi, guint64 * offset,
     res = gst_avi_demux_peek_tag (avi, off, tag, &_size);
     if (res != GST_FLOW_OK)
       break;
-    if (*tag == GST_RIFF_TAG_LIST)
+    if (*tag == GST_RIFF_TAG_LIST || *tag == GST_RIFF_TAG_RIFF)
       off += 8 + 4;             /* skip tag + size + subtag */
     else {
       *offset = off + 8;
@@ -2714,7 +2747,6 @@ out_of_mem:
 static void
 gst_avi_demux_calculate_durations_from_index (GstAviDemux * avi)
 {
-  gst_avi_index_entry *entry;
   gint stream;
   GstClockTime total;
 
@@ -2728,26 +2760,12 @@ gst_avi_demux_calculate_durations_from_index (GstAviDemux * avi)
 
     if (!strh)
       continue;
-    /* get header duration */
-    hduration = gst_util_uint64_scale ((guint64) strh->length *
-        strh->scale, GST_SECOND, (guint64) strh->rate);
-
-    GST_INFO ("Stream %d duration according to header: %" GST_TIME_FORMAT,
-        stream, GST_TIME_ARGS (hduration));
 
-    if (hduration == 0)
-      hduration = GST_CLOCK_TIME_NONE;
+    /* get header duration for the stream */
+    hduration = streamc->hdr_duration;
 
-    /* set duration for the stream */
-    streamc->hdr_duration = hduration;
-
-    /* get last index entry to get duration */
-    if ((entry = gst_avi_demux_index_last (avi, stream)))
-      duration = entry->ts + entry->dur;
-    else
-      duration = GST_CLOCK_TIME_NONE;
-
-    streamc->idx_duration = duration;
+    /* index duration calculated during parsing, invariant under massage */
+    duration = streamc->idx_duration;
 
     /* now pick a good duration */
     if (GST_CLOCK_TIME_IS_VALID (duration)) {
@@ -3074,6 +3092,50 @@ header_wrong_avih:
   }
 }
 
+/* Return TRUE if durations look OK */
+static gboolean
+gst_avi_demux_check_header_durations (GstAviDemux * avi)
+{
+  GstClockTime duration = 0;
+  gint stream;
+
+  /* start with global header duration */
+  duration = avi->avih->us_frame * avi->avih->tot_frames * GST_USECOND;
+  GST_DEBUG_OBJECT (avi, "avih duration: %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (duration));
+  /* unfortunately some ODML files do not seem to set proper tot_frames,
+   * but hey ... */
+
+  if (duration == 0)
+    goto invalid;
+
+  for (stream = 0; stream < avi->num_streams; stream++) {
+    avi_stream_context *streamc = &avi->stream[stream];
+    GstClockTime diff;
+    gint64 var;
+
+    if (!GST_CLOCK_TIME_IS_VALID (streamc->hdr_duration) ||
+        streamc->hdr_duration == 0)
+      goto invalid;
+
+    diff = ABS (GST_CLOCK_DIFF (duration, streamc->hdr_duration));
+    if (diff == 0)
+      continue;
+
+    var = duration / diff;
+    GST_DEBUG_OBJECT (avi, "duration difference: %" GST_TIME_FORMAT
+        ", duration var: %" G_GUINT64_FORMAT, GST_TIME_ARGS (diff), var);
+    /* we are ok with a 1% variation */
+    if (var < 100)
+      goto invalid;
+  }
+
+  return TRUE;
+
+invalid:
+  return FALSE;
+}
+
 /*
  * Read full AVI headers.
  */
@@ -3253,6 +3315,17 @@ gst_avi_demux_stream_header_pull (GstAviDemux * avi)
   } while (1);
 skipping_done:
 
+  /* heuristic to trust header provided durations,
+   * so we can skip index parsing */
+  if (avi->tag_mode == GST_TAG_READING_MODE_TAGS_ONLY) {
+    GST_DEBUG_OBJECT (avi, "tagreading only mode; checking header durations");
+    if (gst_avi_demux_check_header_durations (avi)) {
+      GST_DEBUG_OBJECT (avi,
+          "header durations look OK, skipping index parsing");
+      goto skip_index;
+    }
+  }
+
   GST_DEBUG_OBJECT (avi, "skipping done ... (streams=%u, stream[0].indexes=%p)",
       avi->num_streams, avi->stream[0].indexes);
 
@@ -3271,12 +3344,19 @@ skipping_done:
   }
 
   /* this is a fatal error */
-  if (!index)
-    goto no_index;
+  if (!index) {
+    /* TODO let a property decide whether or not to scan/build index or fail
+     * in normal mode */
+    if (avi->tag_mode == GST_TAG_READING_MODE_TAGS_ONLY)
+      goto skip_index;
+    else
+      goto no_index;
+  }
 
   if (!gst_avi_demux_massage_index (avi, index, alloc))
     goto no_index;
 
+skip_index:
   gst_avi_demux_calculate_durations_from_index (avi);
 
   /* create initial NEWSEGMENT event */
@@ -3954,6 +4034,51 @@ short_buffer:
   }
 }
 
+static GstFlowReturn
+gst_avi_demux_next_data_buffer_to_adapter (GstAviDemux * avi)
+{
+  guint64 pos;
+  guint32 tag;
+  guint size;
+  GstFlowReturn res;
+  GstBuffer *buffer;
+
+  GST_DEBUG ("start offset: %d", (gint32) avi->offset);
+  pos = avi->offset;
+  res = gst_avi_demux_next_data_buffer (avi, &pos, &tag, &size);
+  if (res != GST_FLOW_OK)
+    goto exit;
+
+  size = (size + 1) & ~0x1;
+  /* back off to tag and size */
+  pos -= 8;
+  size += 8;
+  res = gst_pad_pull_range (avi->sinkpad, pos, size, &buffer);
+  if (res != GST_FLOW_OK)
+    goto exit;
+
+  avi->offset = pos + size;
+  GST_DEBUG ("end offset: %d", (gint32) avi->offset);
+
+  if (GST_BUFFER_SIZE (buffer) != size)
+    goto wrong_size;
+
+  gst_adapter_push (avi->adapter, buffer);
+
+exit:
+  return res;
+
+  /* ERRORS */
+wrong_size:
+  {
+    /* short read, we return UNEXPECTED to mark the EOS case */
+    GST_DEBUG_OBJECT (avi, "not enough data (available=%u, needed=%u)",
+        GST_BUFFER_SIZE (buffer), size);
+    gst_buffer_unref (buffer);
+    return GST_FLOW_UNEXPECTED;
+  }
+}
+
 /*
  * Read data. If we have an index it delegates to
  * gst_avi_demux_process_next_entry().
@@ -3972,6 +4097,13 @@ gst_avi_demux_stream_data (GstAviDemux * avi)
   if (avi->index_entries != NULL)
     return gst_avi_demux_process_next_entry (avi);
 
+  /* get data sequentially if we end up here in pull mode without index */
+  if (!avi->streaming) {
+    res = gst_avi_demux_next_data_buffer_to_adapter (avi);
+    if (res != GST_FLOW_OK)
+      return res;
+  }
+
   if (avi->have_eos) {
     /* Clean adapter, we're done */
     gst_adapter_clear (avi->adapter);
@@ -4047,7 +4179,6 @@ gst_avi_demux_stream_data (GstAviDemux * avi)
       /* recoverable */
       GST_WARNING ("Invalid stream ID %d (%" GST_FOURCC_FORMAT ")",
           stream_nr, GST_FOURCC_ARGS (tag));
-      avi->offset += 8 + ((size + 1) & ~1);
       gst_adapter_flush (avi->adapter, 8 + ((size + 1) & ~1));
     } else {
       avi_stream_context *stream;
@@ -4060,7 +4191,6 @@ gst_avi_demux_stream_data (GstAviDemux * avi)
       buf = gst_adapter_take_buffer (avi->adapter, ((size + 1) & ~1));
       /* patch the size */
       GST_BUFFER_SIZE (buf) = size;
-      avi->offset += 8 + ((size + 1) & ~1);
 
       /* get time of this buffer */
       stream = &avi->stream[stream_nr];
@@ -4340,6 +4470,11 @@ gst_avi_demux_change_state (GstElement * element, GstStateChange transition)
   GstAviDemux *avi = GST_AVI_DEMUX (element);
 
   switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      avi->tag_mode =
+          gst_tag_reader_get_tag_reading_mode (GST_TAG_READER (avi));
+      GST_INFO_OBJECT (avi, "tag reading mode: %d", avi->tag_mode);
+      break;
     case GST_STATE_CHANGE_READY_TO_PAUSED:
       avi->streaming = FALSE;
       gst_segment_init (&avi->segment, GST_FORMAT_TIME);
diff --git a/gst/avi/gstavidemux.h b/gst/avi/gstavidemux.h
index 1537f91..c8e5991 100644
--- a/gst/avi/gstavidemux.h
+++ b/gst/avi/gstavidemux.h
@@ -24,8 +24,9 @@
 #include <gst/gst.h>
 
 #include "avi-ids.h"
-#include "gst/riff/riff-ids.h"
-#include "gst/riff/riff-read.h"
+#include <gst/riff/riff-ids.h>
+#include <gst/riff/riff-read.h>
+#include <gst/tag/gsttagreader.h>
 #include <gst/base/gstadapter.h>
 
 G_BEGIN_DECLS
@@ -166,6 +167,7 @@ typedef struct _GstAviDemux {
   GstTagList	*globaltags;
   gboolean	got_tags;
 
+  GstTagReadingMode tag_mode;
 } GstAviDemux;
 
 typedef struct _GstAviDemuxClass {
