avidemux: Also extract IDIT tags present too early
[platform/upstream/gst-plugins-good.git] / gst / avi / gstavidemux.c
index f41c379..6bcdffd 100644 (file)
 
 #define DIV_ROUND_UP(s,v) (((s) + ((v)-1)) / (v))
 
+#define GST_AVI_KEYFRAME 1
+#define ENTRY_IS_KEYFRAME(e) ((e)->flags == GST_AVI_KEYFRAME)
+#define ENTRY_SET_KEYFRAME(e) ((e)->flags = GST_AVI_KEYFRAME)
+#define ENTRY_UNSET_KEYFRAME(e) ((e)->flags = 0)
+
+
 GST_DEBUG_CATEGORY_STATIC (avidemux_debug);
 #define GST_CAT_DEFAULT avidemux_debug
 
@@ -145,13 +151,6 @@ gst_avi_demux_get_type (void)
 static void
 gst_avi_demux_base_init (GstAviDemuxClass * klass)
 {
-  static const GstElementDetails gst_avi_demux_details =
-      GST_ELEMENT_DETAILS ("Avi demuxer",
-      "Codec/Demuxer",
-      "Demultiplex an avi file into audio and video",
-      "Erik Walthinsen <omega@cse.ogi.edu>\n"
-      "Wim Taymans <wim.taymans@chello.be>\n"
-      "Thijs Vermeir <thijsvermeir@gmail.com>");
   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
   GstPadTemplate *videosrctempl, *audiosrctempl, *subsrctempl;
   GstCaps *audcaps, *vidcaps, *subcaps;
@@ -175,7 +174,12 @@ gst_avi_demux_base_init (GstAviDemuxClass * klass)
   gst_element_class_add_pad_template (element_class, subsrctempl);
   gst_element_class_add_pad_template (element_class,
       gst_static_pad_template_get (&sink_templ));
-  gst_element_class_set_details (element_class, &gst_avi_demux_details);
+  gst_element_class_set_details_simple (element_class, "Avi demuxer",
+      "Codec/Demuxer",
+      "Demultiplex an avi file into audio and video",
+      "Erik Walthinsen <omega@cse.ogi.edu>, "
+      "Wim Taymans <wim.taymans@chello.be>, "
+      "Thijs Vermeir <thijsvermeir@gmail.com>");
 }
 
 static void
@@ -275,6 +279,7 @@ gst_avi_demux_reset (GstAviDemux * avi)
 
   avi->state = GST_AVI_DEMUX_START;
   avi->offset = 0;
+  avi->building_index = FALSE;
 
   avi->index_offset = 0;
   g_free (avi->avih);
@@ -284,10 +289,18 @@ gst_avi_demux_reset (GstAviDemux * avi)
     gst_object_unref (avi->element_index);
   avi->element_index = NULL;
 
+  if (avi->close_seg_event) {
+    gst_event_unref (avi->close_seg_event);
+    avi->close_seg_event = NULL;
+  }
   if (avi->seg_event) {
     gst_event_unref (avi->seg_event);
     avi->seg_event = NULL;
   }
+  if (avi->seek_event) {
+    gst_event_unref (avi->seek_event);
+    avi->seek_event = NULL;
+  }
 
   if (avi->globaltags)
     gst_tag_list_free (avi->globaltags);
@@ -635,9 +648,9 @@ gst_avi_demux_seek_streams (GstAviDemux * avi, guint64 offset, gboolean before)
 
     if (before) {
       if (entry) {
+        gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &val);
         GST_DEBUG_OBJECT (avi, "stream %d, previous entry at %"
             G_GUINT64_FORMAT, i, val);
-        gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &val);
         if (val < min)
           min = val;
       }
@@ -688,20 +701,23 @@ gst_avi_demux_seek_streams_index (GstAviDemux * avi, guint64 offset,
   for (i = 0; i < avi->num_streams; i++) {
     stream = &avi->stream[i];
 
+    /* compensate for chunk header */
+    offset += 8;
     entry =
         gst_util_array_binary_search (stream->index, stream->idx_n,
         sizeof (GstAviIndexEntry),
         (GCompareDataFunc) gst_avi_demux_index_entry_offset_search,
         before ? GST_SEARCH_MODE_BEFORE : GST_SEARCH_MODE_AFTER, &offset, NULL);
+    offset -= 8;
 
     if (entry)
       index = entry - stream->index;
 
     if (before) {
       if (entry) {
+        val = stream->index[index].offset;
         GST_DEBUG_OBJECT (avi,
             "stream %d, previous entry at %" G_GUINT64_FORMAT, i, val);
-        val = stream->index[index].offset;
         if (val < min)
           min = val;
       }
@@ -715,13 +731,11 @@ gst_avi_demux_seek_streams_index (GstAviDemux * avi, guint64 offset,
       continue;
     }
 
-    val = stream->index[index].offset;
+    val = stream->index[index].offset - 8;
     GST_DEBUG_OBJECT (avi, "stream %d, next entry at %" G_GUINT64_FORMAT, i,
         val);
 
-    gst_avi_demux_get_buffer_info (avi, stream, index, (GstClockTime *) & val,
-        NULL, NULL, NULL);
-    stream->current_total = val;
+    stream->current_total = stream->index[index].total;
     stream->current_entry = index;
   }
 
@@ -772,25 +786,45 @@ gst_avi_demux_handle_sink_event (GstPad * pad, GstEvent * event)
 
       if (avi->have_index) {
         GstAviIndexEntry *entry;
-        guint index;
-        /* FIXME, this code assumes the main stream with keyframes is stream 0,
-         * which is mostly correct... */
-        GstAviStream *stream = &avi->stream[avi->main_stream];
-
-        /* find the index for start bytes offset, calculate the corresponding
-         * time and (reget?) start offset in bytes */
-        entry = gst_util_array_binary_search (stream->index,
-            stream->idx_n, sizeof (GstAviIndexEntry),
-            (GCompareDataFunc) gst_avi_demux_index_entry_offset_search,
-            GST_SEARCH_MODE_BEFORE, &start, NULL);
-
-        if (entry == NULL) {
-          index = 0;
-        } else {
+        guint i = 0, index = 0, k = 0;
+        GstAviStream *stream;
+
+        /* compensate chunk header, stored index offset points after header */
+        start += 8;
+        /* find which stream we're on */
+        do {
+          stream = &avi->stream[i];
+
+          /* find the index for start bytes offset */
+          entry = gst_util_array_binary_search (stream->index,
+              stream->idx_n, sizeof (GstAviIndexEntry),
+              (GCompareDataFunc) gst_avi_demux_index_entry_offset_search,
+              GST_SEARCH_MODE_AFTER, &start, NULL);
+
+          if (entry == NULL)
+            continue;
           index = entry - stream->index;
+
+          /* we are on the stream with a chunk start offset closest to start */
+          if (!offset || stream->index[index].offset < offset) {
+            offset = stream->index[index].offset;
+            k = i;
+          }
+          /* exact match needs no further searching */
+          if (stream->index[index].offset == start)
+            break;
+        } while (++i < avi->num_streams);
+        start -= 8;
+        offset -= 8;
+        stream = &avi->stream[k];
+
+        /* so we have no idea what is to come, or where we are */
+        if (!offset) {
+          GST_WARNING_OBJECT (avi, "insufficient index data, forcing EOS");
+          goto eos;
         }
 
-        start = stream->index[index].offset;
+        /* get the ts corresponding to start offset bytes for the stream */
         gst_avi_demux_get_buffer_info (avi, stream, index,
             (GstClockTime *) & time, NULL, NULL, NULL);
       } else if (avi->element_index) {
@@ -808,13 +842,12 @@ gst_avi_demux_handle_sink_event (GstPad * pad, GstEvent * event)
         }
 
         gst_index_entry_assoc_map (entry, GST_FORMAT_TIME, &time);
-        gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &start);
+        gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &offset);
       } else {
         GST_WARNING_OBJECT (avi, "no index data, forcing EOS");
         goto eos;
       }
 
-      offset = start;
       stop = GST_CLOCK_TIME_NONE;
 
       /* set up segment and send downstream */
@@ -831,15 +864,15 @@ gst_avi_demux_handle_sink_event (GstPad * pad, GstEvent * event)
       GST_DEBUG_OBJECT (avi, "next chunk expected at %" G_GINT64_FORMAT, start);
 
       /* adjust state for streaming thread accordingly */
-      avi->offset = offset;
       if (avi->have_index)
         gst_avi_demux_seek_streams_index (avi, offset, FALSE);
       else
         gst_avi_demux_seek_streams (avi, offset, FALSE);
 
       /* set up streaming thread */
-      avi->offset = offset;
-      avi->todrop = start - offset;
+      g_assert (offset >= start);
+      avi->offset = start;
+      avi->todrop = offset - start;
 
     exit:
       gst_event_unref (event);
@@ -1564,7 +1597,7 @@ out_of_mem:
  * Create and push a flushing seek event upstream
  */
 static gboolean
-avi_demux_do_push_seek (GstAviDemux * demux, guint64 offset)
+perform_seek_to_offset (GstAviDemux * demux, guint64 offset)
 {
   GstEvent *event;
   gboolean res = 0;
@@ -1591,6 +1624,7 @@ gst_avi_demux_read_subindexes_push (GstAviDemux * avi)
 {
   guint32 tag = 0, size;
   GstBuffer *buf = NULL;
+  guint odml_stream;
 
   GST_DEBUG_OBJECT (avi, "read subindexes for %d streams", avi->num_streams);
 
@@ -1600,10 +1634,13 @@ gst_avi_demux_read_subindexes_push (GstAviDemux * avi)
   if (!gst_avi_demux_peek_chunk (avi, &tag, &size))
     return TRUE;
 
-  if ((tag != GST_MAKE_FOURCC ('i', 'x', '0' + avi->odml_stream / 10,
-              '0' + avi->odml_stream % 10)) &&
-      (tag != GST_MAKE_FOURCC ('0' + avi->odml_stream / 10,
-              '0' + avi->odml_stream % 10, 'i', 'x'))) {
+  /* this is the ODML chunk we expect */
+  odml_stream = avi->odml_stream;
+
+  if ((tag != GST_MAKE_FOURCC ('i', 'x', '0' + odml_stream / 10,
+              '0' + odml_stream % 10)) &&
+      (tag != GST_MAKE_FOURCC ('0' + odml_stream / 10,
+              '0' + odml_stream % 10, 'i', 'x'))) {
     GST_WARNING_OBJECT (avi, "Not an ix## chunk (%" GST_FOURCC_FORMAT ")",
         GST_FOURCC_ARGS (tag));
     return FALSE;
@@ -1614,22 +1651,31 @@ gst_avi_demux_read_subindexes_push (GstAviDemux * avi)
   gst_adapter_flush (avi->adapter, 8);
   buf = gst_adapter_take_buffer (avi->adapter, size);
 
-  if (!gst_avi_demux_parse_subindex (avi, &avi->stream[avi->odml_stream], buf))
+  if (!gst_avi_demux_parse_subindex (avi, &avi->stream[odml_stream], buf))
     return FALSE;
 
-  if (avi->odml_subidxs[++avi->odml_subidx] == GST_BUFFER_OFFSET_NONE) {
+  /* we parsed the index, go to next subindex */
+  avi->odml_subidx++;
+
+  if (avi->odml_subidxs[avi->odml_subidx] == GST_BUFFER_OFFSET_NONE) {
+    /* we reached the end of the indexes for this stream, move to the next
+     * stream to handle the first index */
+    avi->odml_stream++;
     avi->odml_subidx = 0;
-    if (++avi->odml_stream < avi->num_streams) {
+
+    if (avi->odml_stream < avi->num_streams) {
+      /* there are more indexes */
       avi->odml_subidxs = avi->stream[avi->odml_stream].indexes;
     } else {
-      /* get stream stats now */
+      /* we're done, get stream stats now */
       avi->have_index = gst_avi_demux_do_index_stats (avi);
 
       return TRUE;
     }
   }
 
-  return avi_demux_do_push_seek (avi, avi->odml_subidxs[avi->odml_subidx]);
+  /* seek to next index */
+  return perform_seek_to_offset (avi, avi->odml_subidxs[avi->odml_subidx]);
 }
 
 /*
@@ -1946,14 +1992,36 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf)
             GST_DEBUG_OBJECT (element, "marking video as VBR, res %d", res);
             break;
           case GST_RIFF_FCC_auds:
-            stream->is_vbr = (stream->strh->samplesize == 0)
-                && stream->strh->scale > 1;
             res =
                 gst_riff_parse_strf_auds (element, sub, &stream->strf.auds,
                 &stream->extradata);
+            stream->is_vbr = (stream->strh->samplesize == 0)
+                && stream->strh->scale > 1
+                && stream->strf.auds->blockalign != 1;
             sub = NULL;
             GST_DEBUG_OBJECT (element, "marking audio as VBR:%d, res %d",
                 stream->is_vbr, res);
+            /* we need these or we have no way to come up with timestamps */
+            if ((!stream->is_vbr && !stream->strf.auds->av_bps) ||
+                (stream->is_vbr && (!stream->strh->scale ||
+                        !stream->strh->rate))) {
+              GST_WARNING_OBJECT (element,
+                  "invalid audio header, ignoring stream");
+              goto fail;
+            }
+            /* some more sanity checks */
+            if (stream->is_vbr) {
+              if (stream->strf.auds->blockalign <= 4) {
+                /* that would mean (too) many frames per chunk,
+                 * so not likely set as expected */
+                GST_DEBUG_OBJECT (element,
+                    "suspicious blockalign %d for VBR audio; "
+                    "overriding to 1 frame per chunk",
+                    stream->strf.auds->blockalign);
+                /* this should top any likely value */
+                stream->strf.auds->blockalign = (1 << 12);
+              }
+            }
             break;
           case GST_RIFF_FCC_iavs:
             stream->is_vbr = TRUE;
@@ -2027,6 +2095,9 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf)
         }
         GST_DEBUG_OBJECT (avi, "stream name: %s", stream->name);
         break;
+      case GST_RIFF_IDIT:
+        gst_avi_demux_parse_idit (avi, sub);
+        break;
       default:
         if (tag == GST_MAKE_FOURCC ('i', 'n', 'd', 'x') ||
             tag == GST_MAKE_FOURCC ('i', 'x', '0' + avi->num_streams / 10,
@@ -2302,7 +2373,7 @@ gst_avi_demux_parse_odml (GstAviDemux * avi, GstBuffer * buf)
 static guint
 gst_avi_demux_index_last (GstAviDemux * avi, GstAviStream * stream)
 {
-  return stream->idx_n - 1;
+  return stream->idx_n;
 }
 
 /* find a previous entry in the index with the given flags */
@@ -2684,7 +2755,7 @@ gst_avi_demux_stream_index_push (GstAviDemux * avi)
         (8 + GST_ROUND_UP_2 (size)));
     avi->idx1_offset = offset + 8 + GST_ROUND_UP_2 (size);
     /* issue seek to allow chain function to handle it and return! */
-    avi_demux_do_push_seek (avi, avi->idx1_offset);
+    perform_seek_to_offset (avi, avi->idx1_offset);
     return;
   }
 
@@ -2705,7 +2776,7 @@ gst_avi_demux_stream_index_push (GstAviDemux * avi)
   GST_DEBUG ("will parse index chunk size %u for tag %"
       GST_FOURCC_FORMAT, GST_BUFFER_SIZE (buf), GST_FOURCC_ARGS (tag));
 
-  avi->offset = avi->first_movi_offset - 8;
+  avi->offset = avi->first_movi_offset;
   gst_avi_demux_parse_index (avi, buf);
 
 #ifndef GST_DISABLE_GST_DEBUG
@@ -2889,7 +2960,6 @@ gst_avi_demux_stream_scan (GstAviDemux * avi)
   /* collect stats */
   avi->have_index = gst_avi_demux_do_index_stats (avi);
 
-
   return TRUE;
 
   /* ERRORS */
@@ -3022,11 +3092,6 @@ gst_avi_demux_check_seekability (GstAviDemux * avi)
     seekable = FALSE;
   }
 
-  if (!avi->element_index) {
-    GST_DEBUG_OBJECT (avi, "no index");
-    seekable = FALSE;
-  }
-
 done:
   GST_INFO_OBJECT (avi, "seekable: %d (%" G_GUINT64_FORMAT " - %"
       G_GUINT64_FORMAT ")", seekable, start, stop);
@@ -3318,9 +3383,12 @@ header_wrong_avih:
 }
 
 static void
-gst_avi_demux_add_date_tag (GstAviDemux * avi, gint y, gint m, gint d)
+gst_avi_demux_add_date_tag (GstAviDemux * avi, gint y, gint m, gint d,
+    gint h, gint min, gint s)
 {
   GDate *date;
+  GstDateTime *dt;
+
   date = g_date_new_dmy (d, m, y);
   if (!g_date_valid (date)) {
     /* bogus date */
@@ -3329,12 +3397,19 @@ gst_avi_demux_add_date_tag (GstAviDemux * avi, gint y, gint m, gint d)
     return;
   }
 
+  dt = gst_date_time_new_local_time (y, m, d, h, min, s);
+
   if (avi->globaltags == NULL)
     avi->globaltags = gst_tag_list_new ();
 
   gst_tag_list_add (avi->globaltags, GST_TAG_MERGE_REPLACE, GST_TAG_DATE, date,
       NULL);
   g_date_free (date);
+  if (dt) {
+    gst_tag_list_add (avi->globaltags, GST_TAG_MERGE_REPLACE, GST_TAG_DATE_TIME,
+        dt, NULL);
+    gst_date_time_unref (dt);
+  }
 }
 
 static void
@@ -3348,7 +3423,7 @@ gst_avi_demux_parse_idit_nums_only (GstAviDemux * avi, gchar * data)
     GST_WARNING_OBJECT (avi, "Failed to parse IDIT tag");
     return;
   }
-  gst_avi_demux_add_date_tag (avi, y, m, d);
+  gst_avi_demux_add_date_tag (avi, y, m, d, 0, 0, 0);
 }
 
 static gint
@@ -3399,7 +3474,7 @@ gst_avi_demux_parse_idit_text (GstAviDemux * avi, gchar * data)
     return;
   }
   month = get_month_num (monthstr, strlen (monthstr));
-  gst_avi_demux_add_date_tag (avi, year, month, day);
+  gst_avi_demux_add_date_tag (avi, year, month, day, hour, min, sec);
 }
 
 static void
@@ -3874,7 +3949,7 @@ gst_avi_demux_do_seek (GstAviDemux * avi, GstSegment * segment)
   GstAviStream *stream;
 
   seek_time = segment->last_stop;
-  keyframe = !!(segment->flags & GST_SEEK_FLAG_KEY_UNIT);
+  keyframe = ! !(segment->flags & GST_SEEK_FLAG_KEY_UNIT);
 
   GST_DEBUG_OBJECT (avi, "seek to: %" GST_TIME_FORMAT
       " keyframe seeking:%d", GST_TIME_ARGS (seek_time), keyframe);
@@ -3937,7 +4012,7 @@ gst_avi_demux_do_seek (GstAviDemux * avi, GstSegment * segment)
 }
 
 /*
- * Handle seek event.
+ * Handle seek event in pull mode.
  */
 static gboolean
 gst_avi_demux_handle_seek (GstAviDemux * avi, GstPad * pad, GstEvent * event)
@@ -4019,30 +4094,21 @@ gst_avi_demux_handle_seek (GstAviDemux * avi, GstPad * pad, GstEvent * event)
    * actually never fails. */
   gst_avi_demux_do_seek (avi, &seeksegment);
 
+  gst_event_replace (&avi->close_seg_event, NULL);
   if (flush) {
     GstEvent *fevent = gst_event_new_flush_stop ();
 
     GST_DEBUG_OBJECT (avi, "sending flush stop");
     gst_avi_demux_push_event (avi, gst_event_ref (fevent));
     gst_pad_push_event (avi->sinkpad, fevent);
-
-    /* reset the last flow and mark discont, FLUSH is always DISCONT */
-    for (i = 0; i < avi->num_streams; i++) {
-      GST_DEBUG_OBJECT (avi, "marking DISCONT");
-      avi->stream[i].last_flow = GST_FLOW_OK;
-      avi->stream[i].discont = TRUE;
-    }
   } else if (avi->segment_running) {
-    GstEvent *seg;
-
     /* we are running the current segment and doing a non-flushing seek,
      * close the segment first based on the last_stop. */
     GST_DEBUG_OBJECT (avi, "closing running segment %" G_GINT64_FORMAT
         " to %" G_GINT64_FORMAT, avi->segment.start, avi->segment.last_stop);
-    seg = gst_event_new_new_segment_full (TRUE,
+    avi->close_seg_event = gst_event_new_new_segment_full (TRUE,
         avi->segment.rate, avi->segment.applied_rate, avi->segment.format,
         avi->segment.start, avi->segment.last_stop, avi->segment.time);
-    gst_avi_demux_push_event (avi, seg);
   }
 
   /* now update the real segment info */
@@ -4079,6 +4145,12 @@ gst_avi_demux_handle_seek (GstAviDemux * avi, GstPad * pad, GstEvent * event)
     gst_pad_start_task (avi->sinkpad, (GstTaskFunction) gst_avi_demux_loop,
         avi->sinkpad);
   }
+  /* reset the last flow and mark discont, seek is always DISCONT */
+  for (i = 0; i < avi->num_streams; i++) {
+    GST_DEBUG_OBJECT (avi, "marking DISCONT");
+    avi->stream[i].last_flow = GST_FLOW_OK;
+    avi->stream[i].discont = TRUE;
+  }
   GST_PAD_STREAM_UNLOCK (avi->sinkpad);
 
   return TRUE;
@@ -4092,7 +4164,7 @@ no_format:
 }
 
 /*
- * Handle seek event.
+ * Handle seek event in push mode.
  */
 static gboolean
 avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad, GstEvent * event)
@@ -4107,6 +4179,8 @@ avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad, GstEvent * event)
   guint index;
   guint n, str_num;
   guint64 min_offset;
+  GstSegment seeksegment;
+  gboolean update;
 
   /* check we have the index */
   if (!avi->have_index) {
@@ -4134,87 +4208,110 @@ avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad, GstEvent * event)
 
     format = fmt;
   }
-  GST_DEBUG_OBJECT (avi,
-      "seek requested: rate %g cur %" GST_TIME_FORMAT " stop %"
-      GST_TIME_FORMAT, rate, GST_TIME_ARGS (cur), GST_TIME_ARGS (stop));
-  /* FIXME: can we do anything with rate!=1.0 */
 
-  keyframe = !!(flags & GST_SEEK_FLAG_KEY_UNIT);
+  /* let gst_segment handle any tricky stuff */
+  GST_DEBUG_OBJECT (avi, "configuring seek");
+  memcpy (&seeksegment, &avi->segment, sizeof (GstSegment));
+  gst_segment_set_seek (&seeksegment, rate, format, flags,
+      cur_type, cur, stop_type, stop, &update);
 
-  GST_DEBUG_OBJECT (avi, "seek to: %" GST_TIME_FORMAT
-      " keyframe seeking:%d", GST_TIME_ARGS (cur), keyframe);
+  keyframe = ! !(flags & GST_SEEK_FLAG_KEY_UNIT);
+  cur = seeksegment.last_stop;
+
+  GST_DEBUG_OBJECT (avi,
+      "Seek requested: ts %" GST_TIME_FORMAT " stop %" GST_TIME_FORMAT
+      ", kf %u, rate %lf", GST_TIME_ARGS (cur), GST_TIME_ARGS (stop), keyframe,
+      rate);
+
+  if (rate < 0) {
+    GST_DEBUG_OBJECT (avi, "negative rate seek not supported in push mode");
+    return FALSE;
+  }
 
   /* FIXME, this code assumes the main stream with keyframes is stream 0,
    * which is mostly correct... */
   str_num = avi->main_stream;
-  stream = &avi->stream[avi->main_stream];
+  stream = &avi->stream[str_num];
 
   /* get the entry index for the requested position */
   index = gst_avi_demux_index_for_time (avi, stream, cur);
-  GST_DEBUG_OBJECT (avi, "Got entry %u", index);
+  GST_DEBUG_OBJECT (avi, "str %u: Found entry %u for %" GST_TIME_FORMAT,
+      str_num, index, GST_TIME_ARGS (cur));
 
   /* check if we are already on a keyframe */
   if (!ENTRY_IS_KEYFRAME (&stream->index[index])) {
-    GST_DEBUG_OBJECT (avi, "not keyframe, searching back");
+    GST_DEBUG_OBJECT (avi, "Entry is not a keyframe - searching back");
     /* now go to the previous keyframe, this is where we should start
      * decoding from. */
     index = gst_avi_demux_index_prev (avi, stream, index, TRUE);
-    GST_DEBUG_OBJECT (avi, "previous keyframe at %u", index);
+    GST_DEBUG_OBJECT (avi, "Found previous keyframe at %u", index);
   }
 
   gst_avi_demux_get_buffer_info (avi, stream, index,
       &stream->current_timestamp, &stream->current_ts_end,
       &stream->current_offset, &stream->current_offset_end);
 
-  min_offset = stream->current_offset;
+  /* re-use cur to be the timestamp of the seek as it _will_ be */
+  cur = stream->current_timestamp;
+
+  min_offset = stream->index[index].offset;
+  avi->seek_kf_offset = min_offset - 8;
+
+  GST_DEBUG_OBJECT (avi,
+      "Seek to: ts %" GST_TIME_FORMAT " (on str %u, idx %u, offset %"
+      G_GUINT64_FORMAT ")", GST_TIME_ARGS (stream->current_timestamp), str_num,
+      index, min_offset);
+
   for (n = 0; n < avi->num_streams; n++) {
     GstAviStream *str = &avi->stream[n];
     guint idx;
-    guint64 off;
 
     if (n == avi->main_stream)
       continue;
 
     /* get the entry index for the requested position */
-    idx = gst_avi_demux_index_for_time (avi, str, stream->current_timestamp);
-    GST_DEBUG_OBJECT (avi, "Got entry %u", idx);
+    idx = gst_avi_demux_index_for_time (avi, str, cur);
+    GST_DEBUG_OBJECT (avi, "str %u: Found entry %u for %" GST_TIME_FORMAT, n,
+        idx, GST_TIME_ARGS (cur));
 
     /* check if we are already on a keyframe */
     if (!ENTRY_IS_KEYFRAME (&str->index[idx])) {
-      GST_DEBUG_OBJECT (avi, "not keyframe, searching back");
+      GST_DEBUG_OBJECT (avi, "Entry is not a keyframe - searching back");
       /* now go to the previous keyframe, this is where we should start
        * decoding from. */
       idx = gst_avi_demux_index_prev (avi, str, idx, TRUE);
-      GST_DEBUG_OBJECT (avi, "previous keyframe at %u", idx);
+      GST_DEBUG_OBJECT (avi, "Found previous keyframe at %u", idx);
     }
 
-    gst_avi_demux_get_buffer_info (avi, str, idx, NULL, NULL, &off, NULL);
-    if (off < min_offset) {
+    gst_avi_demux_get_buffer_info (avi, str, idx,
+        &str->current_timestamp, &str->current_ts_end,
+        &str->current_offset, &str->current_offset_end);
+
+    if (str->index[idx].offset < min_offset) {
+      min_offset = str->index[idx].offset;
       GST_DEBUG_OBJECT (avi,
-          "Found an earlier offset at %" G_GUINT64_FORMAT ", str %u", off, n);
-      min_offset = off;
+          "Found an earlier offset at %" G_GUINT64_FORMAT ", str %u",
+          min_offset, n);
       str_num = n;
       stream = str;
       index = idx;
     }
   }
 
-  gst_avi_demux_get_buffer_info (avi, stream, index,
-      &stream->current_timestamp, &stream->current_ts_end,
-      &stream->current_offset, &stream->current_offset_end);
-
-  GST_DEBUG_OBJECT (avi, "Moved to str %u, idx %u, ts %" GST_TIME_FORMAT
-      ", ts_end %" GST_TIME_FORMAT ", off %" G_GUINT64_FORMAT
-      ", off_end %" G_GUINT64_FORMAT, str_num, index,
+  GST_DEBUG_OBJECT (avi,
+      "Seek performed: str %u, offset %" G_GUINT64_FORMAT ", idx %u, ts %"
+      GST_TIME_FORMAT ", ts_end %" GST_TIME_FORMAT ", off %" G_GUINT64_FORMAT
+      ", off_end %" G_GUINT64_FORMAT, str_num, min_offset, index,
       GST_TIME_ARGS (stream->current_timestamp),
       GST_TIME_ARGS (stream->current_ts_end), stream->current_offset,
       stream->current_offset_end);
 
-  GST_DEBUG_OBJECT (avi, "Seeking to offset %" G_GUINT64_FORMAT,
-      stream->index[index].offset);
+  /* index data refers to data, not chunk header (for pull mode convenience) */
+  min_offset -= 8;
+  GST_DEBUG_OBJECT (avi, "seeking to chunk at offset %" G_GUINT64_FORMAT,
+      min_offset);
 
-  if (!avi_demux_do_push_seek (avi,
-          stream->index[index].offset - (avi->stream[0].indexes ? 8 : 0))) {
+  if (!perform_seek_to_offset (avi, min_offset)) {
     GST_DEBUG_OBJECT (avi, "seek event failed!");
     return FALSE;
   }
@@ -4232,8 +4329,10 @@ gst_avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad,
 {
   /* check for having parsed index already */
   if (!avi->have_index) {
-    guint64 offset;
+    guint64 offset = 0;
+    gboolean building_index;
 
+    GST_OBJECT_LOCK (avi);
     /* handle the seek event in the chain function */
     avi->state = GST_AVI_DEMUX_SEEK;
 
@@ -4242,8 +4341,11 @@ gst_avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad,
       gst_event_unref (avi->seek_event);
     avi->seek_event = gst_event_ref (event);
 
-    if (!avi->building_index) {
-      avi->building_index = 1;
+    /* set the building_index flag so that only one thread can setup the
+     * structures for index seeking. */
+    building_index = avi->building_index;
+    if (!building_index) {
+      avi->building_index = TRUE;
       if (avi->stream[0].indexes) {
         avi->odml_stream = 0;
         avi->odml_subidxs = avi->stream[avi->odml_stream].indexes;
@@ -4251,12 +4353,15 @@ gst_avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad,
       } else {
         offset = avi->idx1_offset;
       }
+    }
+    GST_OBJECT_UNLOCK (avi);
 
+    if (!building_index) {
       /* seek to the first subindex or legacy index */
       GST_INFO_OBJECT (avi,
           "Seeking to legacy index/first subindex at %" G_GUINT64_FORMAT,
           offset);
-      return avi_demux_do_push_seek (avi, offset);
+      return perform_seek_to_offset (avi, offset);
     }
 
     /* FIXME: we have to always return true so that we don't block the seek
@@ -4411,7 +4516,7 @@ gst_avi_demux_advance (GstAviDemux * avi, GstAviStream * stream,
   new_entry = old_entry + 1;
 
   /* see if we reached the end */
-  if (new_entry > stream->stop_entry) {
+  if (new_entry >= stream->stop_entry) {
     if (avi->segment.rate < 0.0) {
       if (stream->step_entry == stream->start_entry) {
         /* we stepped all the way to the start, eos */
@@ -4783,9 +4888,13 @@ gst_avi_demux_stream_data (GstAviDemux * avi)
        * through the whole file */
       if (avi->abort_buffering) {
         avi->abort_buffering = FALSE;
-        gst_adapter_flush (avi->adapter, 8);
+        if (size) {
+          gst_adapter_flush (avi->adapter, 8);
+          return GST_FLOW_OK;
+        }
+      } else {
+        return GST_FLOW_OK;
       }
-      return GST_FLOW_OK;
     }
     GST_DEBUG ("chunk ID %" GST_FOURCC_FORMAT ", size %u",
         GST_FOURCC_ARGS (tag), size);
@@ -4801,15 +4910,32 @@ gst_avi_demux_stream_data (GstAviDemux * avi)
     } else {
       GstAviStream *stream;
       GstClockTime next_ts = 0;
-      GstBuffer *buf;
+      GstBuffer *buf = NULL;
       guint64 offset;
+      gboolean saw_desired_kf = stream_nr != avi->main_stream
+          || avi->offset >= avi->seek_kf_offset;
 
-      gst_adapter_flush (avi->adapter, 8);
+      if (stream_nr == avi->main_stream && avi->offset == avi->seek_kf_offset) {
+        GST_DEBUG_OBJECT (avi, "Desired keyframe reached");
+        avi->seek_kf_offset = 0;
+      }
+
+      if (saw_desired_kf) {
+        gst_adapter_flush (avi->adapter, 8);
+        /* get buffer */
+        if (size) {
+          buf = gst_adapter_take_buffer (avi->adapter, GST_ROUND_UP_2 (size));
+          /* patch the size */
+          GST_BUFFER_SIZE (buf) = size;
+        } else {
+          buf = NULL;
+        }
+      } else {
+        GST_DEBUG_OBJECT (avi,
+            "Desired keyframe not yet reached, flushing chunk");
+        gst_adapter_flush (avi->adapter, 8 + GST_ROUND_UP_2 (size));
+      }
 
-      /* get buffer */
-      buf = gst_adapter_take_buffer (avi->adapter, GST_ROUND_UP_2 (size));
-      /* patch the size */
-      GST_BUFFER_SIZE (buf) = size;
       offset = avi->offset;
       avi->offset += 8 + GST_ROUND_UP_2 (size);
 
@@ -4825,10 +4951,9 @@ gst_avi_demux_stream_data (GstAviDemux * avi)
       if (G_UNLIKELY (!stream->pad)) {
         GST_WARNING_OBJECT (avi, "no pad for stream ID %" GST_FOURCC_FORMAT,
             GST_FOURCC_ARGS (tag));
-        gst_buffer_unref (buf);
+        if (buf)
+          gst_buffer_unref (buf);
       } else {
-        GstClockTime dur_ts = 0;
-
         /* get time of this buffer */
         gst_pad_query_position (stream->pad, &format, (gint64 *) & next_ts);
         if (G_UNLIKELY (format != GST_FORMAT_TIME))
@@ -4840,47 +4965,52 @@ gst_avi_demux_stream_data (GstAviDemux * avi)
         stream->current_entry++;
         stream->current_total += size;
 
-        /* invert the picture if needed */
-        buf = gst_avi_demux_invert (stream, buf);
-
-        gst_pad_query_position (stream->pad, &format, (gint64 *) & dur_ts);
-        if (G_UNLIKELY (format != GST_FORMAT_TIME))
-          goto wrong_format;
+        /* update current position in the segment */
+        gst_segment_set_last_stop (&avi->segment, GST_FORMAT_TIME, next_ts);
 
-        GST_BUFFER_TIMESTAMP (buf) = next_ts;
-        GST_BUFFER_DURATION (buf) = dur_ts - next_ts;
-        if (stream->strh->type == GST_RIFF_FCC_vids) {
-          GST_BUFFER_OFFSET (buf) = stream->current_entry - 1;
-          GST_BUFFER_OFFSET_END (buf) = stream->current_entry;
-        } else {
-          GST_BUFFER_OFFSET (buf) = GST_BUFFER_OFFSET_NONE;
-          GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET_NONE;
-        }
+        if (saw_desired_kf && buf) {
+          GstClockTime dur_ts = 0;
 
-        gst_buffer_set_caps (buf, GST_PAD_CAPS (stream->pad));
-        GST_DEBUG_OBJECT (avi,
-            "Pushing buffer with time=%" GST_TIME_FORMAT ", duration %"
-            GST_TIME_FORMAT ", offset %" G_GUINT64_FORMAT
-            " and size %d over pad %s", GST_TIME_ARGS (next_ts),
-            GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), GST_BUFFER_OFFSET (buf),
-            size, GST_PAD_NAME (stream->pad));
+          /* invert the picture if needed */
+          buf = gst_avi_demux_invert (stream, buf);
 
-        /* update current position in the segment */
-        gst_segment_set_last_stop (&avi->segment, GST_FORMAT_TIME, next_ts);
+          gst_pad_query_position (stream->pad, &format, (gint64 *) & dur_ts);
+          if (G_UNLIKELY (format != GST_FORMAT_TIME))
+            goto wrong_format;
 
-        /* mark discont when pending */
-        if (G_UNLIKELY (stream->discont)) {
-          GST_DEBUG_OBJECT (avi, "Setting DISCONT");
-          GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
-          stream->discont = FALSE;
-        }
-        res = gst_pad_push (stream->pad, buf);
+          GST_BUFFER_TIMESTAMP (buf) = next_ts;
+          GST_BUFFER_DURATION (buf) = dur_ts - next_ts;
+          if (stream->strh->type == GST_RIFF_FCC_vids) {
+            GST_BUFFER_OFFSET (buf) = stream->current_entry - 1;
+            GST_BUFFER_OFFSET_END (buf) = stream->current_entry;
+          } else {
+            GST_BUFFER_OFFSET (buf) = GST_BUFFER_OFFSET_NONE;
+            GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET_NONE;
+          }
 
-        /* combine flows */
-        res = gst_avi_demux_combine_flows (avi, stream, res);
-        if (G_UNLIKELY (res != GST_FLOW_OK)) {
-          GST_DEBUG ("Push failed; %s", gst_flow_get_name (res));
-          return res;
+          gst_buffer_set_caps (buf, GST_PAD_CAPS (stream->pad));
+          GST_DEBUG_OBJECT (avi,
+              "Pushing buffer with time=%" GST_TIME_FORMAT ", duration %"
+              GST_TIME_FORMAT ", offset %" G_GUINT64_FORMAT
+              " and size %d over pad %s", GST_TIME_ARGS (next_ts),
+              GST_TIME_ARGS (GST_BUFFER_DURATION (buf)),
+              GST_BUFFER_OFFSET (buf), size, GST_PAD_NAME (stream->pad));
+
+          /* mark discont when pending */
+          if (G_UNLIKELY (stream->discont)) {
+            GST_DEBUG_OBJECT (avi, "Setting DISCONT");
+            GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
+            stream->discont = FALSE;
+          }
+          res = gst_pad_push (stream->pad, buf);
+          buf = NULL;
+
+          /* combine flows */
+          res = gst_avi_demux_combine_flows (avi, stream, res);
+          if (G_UNLIKELY (res != GST_FLOW_OK)) {
+            GST_DEBUG ("Push failed; %s", gst_flow_get_name (res));
+            return res;
+          }
         }
       }
     }
@@ -4963,6 +5093,10 @@ gst_avi_demux_loop (GstPad * pad)
       avi->state = GST_AVI_DEMUX_MOVI;
       break;
     case GST_AVI_DEMUX_MOVI:
+      if (G_UNLIKELY (avi->close_seg_event)) {
+        gst_avi_demux_push_event (avi, avi->close_seg_event);
+        avi->close_seg_event = NULL;
+      }
       if (G_UNLIKELY (avi->seg_event)) {
         gst_avi_demux_push_event (avi, avi->seg_event);
         avi->seg_event = NULL;
@@ -4988,13 +5122,13 @@ gst_avi_demux_loop (GstPad * pad)
   return;
 
   /* ERRORS */
-pause:
-  GST_LOG_OBJECT (avi, "pausing task, reason %s", gst_flow_get_name (res));
-  avi->segment_running = FALSE;
-  gst_pad_pause_task (avi->sinkpad);
+pause:{
+
+    gboolean push_eos = FALSE;
+    GST_LOG_OBJECT (avi, "pausing task, reason %s", gst_flow_get_name (res));
+    avi->segment_running = FALSE;
+    gst_pad_pause_task (avi->sinkpad);
 
-  if (GST_FLOW_IS_FATAL (res) || (res == GST_FLOW_NOT_LINKED)) {
-    gboolean push_eos = TRUE;
 
     if (res == GST_FLOW_UNEXPECTED) {
       /* handle end-of-stream/segment */
@@ -5010,13 +5144,17 @@ pause:
             (GST_ELEMENT_CAST (avi),
             gst_message_new_segment_done (GST_OBJECT_CAST (avi),
                 GST_FORMAT_TIME, stop));
-        push_eos = FALSE;
+      } else {
+        push_eos = TRUE;
       }
-    } else {
-      /* for fatal errors we post an error message */
+    } else if (res == GST_FLOW_NOT_LINKED || res < GST_FLOW_UNEXPECTED) {
+      /* for fatal errors we post an error message, wrong-state is
+       * not fatal because it happens due to flushes and only means
+       * that we should stop now. */
       GST_ELEMENT_ERROR (avi, STREAM, FAILED,
           (_("Internal data stream error.")),
           ("streaming stopped, reason %s", gst_flow_get_name (res)));
+      push_eos = TRUE;
     }
     if (push_eos) {
       GST_INFO_OBJECT (avi, "sending eos");
@@ -5045,9 +5183,6 @@ gst_avi_demux_chain (GstPad * pad, GstBuffer * buf)
       avi->stream[i].discont = TRUE;
   }
 
-  if (GST_BUFFER_IS_DISCONT (buf))
-    gst_adapter_clear (avi->adapter);
-
   GST_DEBUG ("Store %d bytes in adapter", GST_BUFFER_SIZE (buf));
   gst_adapter_push (avi->adapter, buf);
 
@@ -5065,6 +5200,10 @@ gst_avi_demux_chain (GstPad * pad, GstBuffer * buf)
       }
       break;
     case GST_AVI_DEMUX_MOVI:
+      if (G_UNLIKELY (avi->close_seg_event)) {
+        gst_avi_demux_push_event (avi, avi->close_seg_event);
+        avi->close_seg_event = NULL;
+      }
       if (G_UNLIKELY (avi->seg_event)) {
         gst_avi_demux_push_event (avi, avi->seg_event);
         avi->seg_event = NULL;
@@ -5075,14 +5214,16 @@ gst_avi_demux_chain (GstPad * pad, GstBuffer * buf)
       res = gst_avi_demux_stream_data (avi);
       break;
     case GST_AVI_DEMUX_SEEK:
+    {
+      GstEvent *event;
+
       res = GST_FLOW_OK;
 
       /* obtain and parse indexes */
-      if (avi->stream[0].indexes && !gst_avi_demux_read_subindexes_push (avi)) {
+      if (avi->stream[0].indexes && !gst_avi_demux_read_subindexes_push (avi))
         /* seek in subindex read function failed */
-        res = GST_FLOW_ERROR;
-        break;
-      }
+        goto index_failed;
+
       if (!avi->stream[0].indexes && !avi->have_index
           && avi->avih->flags & GST_RIFF_AVIH_HASINDEX)
         gst_avi_demux_stream_index_push (avi);
@@ -5095,14 +5236,19 @@ gst_avi_demux_chain (GstPad * pad, GstBuffer * buf)
         break;
       }
 
+      GST_OBJECT_LOCK (avi);
+      event = avi->seek_event;
+      avi->seek_event = NULL;
+      GST_OBJECT_UNLOCK (avi);
+
       /* calculate and perform seek */
-      if (!avi_demux_handle_seek_push (avi, avi->sinkpad, avi->seek_event)) {
-        GST_WARNING ("Push mode seek failed");
-        res = GST_FLOW_ERROR;
-      }
-      gst_event_unref (avi->seek_event);
+      if (!avi_demux_handle_seek_push (avi, avi->sinkpad, event))
+        goto seek_failed;
+
+      gst_event_unref (event);
       avi->state = GST_AVI_DEMUX_MOVI;
       break;
+    }
     default:
       GST_ELEMENT_ERROR (avi, STREAM, FAILED, (NULL),
           ("Illegal internal state"));
@@ -5113,13 +5259,28 @@ gst_avi_demux_chain (GstPad * pad, GstBuffer * buf)
   GST_DEBUG_OBJECT (avi, "state: %d res:%s", avi->state,
       gst_flow_get_name (res));
 
-  if (G_UNLIKELY (avi->abort_buffering)) {
+  if (G_UNLIKELY (avi->abort_buffering))
+    goto abort_buffering;
+
+  return res;
+
+  /* ERRORS */
+index_failed:
+  {
+    GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("failed to read indexes"));
+    return GST_FLOW_ERROR;
+  }
+seek_failed:
+  {
+    GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("push mode seek failed"));
+    return GST_FLOW_ERROR;
+  }
+abort_buffering:
+  {
     avi->abort_buffering = FALSE;
-    res = GST_FLOW_ERROR;
     GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("unhandled buffer size"));
+    return GST_FLOW_ERROR;
   }
-
-  return res;
 }
 
 static gboolean
@@ -5234,6 +5395,7 @@ gst_avi_demux_change_state (GstElement * element, GstStateChange transition)
 
   switch (transition) {
     case GST_STATE_CHANGE_PAUSED_TO_READY:
+      avi->have_index = FALSE;
       gst_avi_demux_reset (avi);
       break;
     default: