matroskademux: support (pull mode) negative seek rate
authorMark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
Tue, 23 Mar 2010 16:34:50 +0000 (17:34 +0100)
committerMark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
Fri, 26 Mar 2010 10:45:41 +0000 (11:45 +0100)
gst/matroska/matroska-demux.c
gst/matroska/matroska-demux.h
gst/matroska/matroska-ids.h

index 4eb48e1..9375717 100644 (file)
@@ -408,6 +408,11 @@ gst_matroska_demux_reset (GstElement * element)
   demux->cluster_time = GST_CLOCK_TIME_NONE;
   demux->cluster_offset = 0;
 
+  demux->from_offset = -1;
+  demux->to_offset = G_MAXINT64;
+  demux->seek_index = NULL;
+  demux->seek_entry = 0;
+
   if (demux->close_segment) {
     gst_event_unref (demux->close_segment);
     demux->close_segment = NULL;
@@ -2019,8 +2024,8 @@ gst_matroska_index_seek_find (GstMatroskaIndex * i1, GstClockTime * time,
 
 static GstMatroskaIndex *
 gst_matroskademux_do_index_seek (GstMatroskaDemux * demux,
-    GstMatroskaTrackContext * track, gint64 seek_pos, gint64 segment_stop,
-    gboolean keyunit)
+    GstMatroskaTrackContext * track, gint64 seek_pos, GArray ** _index,
+    gint * _entry_index)
 {
   GstMatroskaIndex *entry = NULL;
   GArray *index;
@@ -2043,6 +2048,11 @@ gst_matroskademux_do_index_seek (GstMatroskaDemux * demux,
   if (entry == NULL)
     entry = &g_array_index (index, GstMatroskaIndex, 0);
 
+  if (_index)
+    *_index = index;
+  if (_entry_index)
+    *_entry_index = entry - (GstMatroskaIndex *) index->data;
+
   return entry;
 }
 
@@ -2156,6 +2166,57 @@ gst_matroska_demux_get_seek_track (GstMatroskaDemux * demux,
 }
 
 static gboolean
+gst_matroska_demux_move_to_entry (GstMatroskaDemux * demux,
+    GstMatroskaIndex * entry, gboolean reset)
+{
+  gint i;
+
+  GST_OBJECT_LOCK (demux);
+
+  /* seek (relative to matroska segment) */
+  if (gst_ebml_read_seek (GST_EBML_READ (demux),
+          entry->pos + demux->ebml_segment_start) != GST_FLOW_OK) {
+    GST_DEBUG_OBJECT (demux, "Failed to seek to offset %" G_GUINT64_FORMAT,
+        entry->pos + demux->ebml_segment_start);
+    goto seek_error;
+  }
+
+  GST_DEBUG_OBJECT (demux, "Seeked to offset %" G_GUINT64_FORMAT ", block %d, "
+      "time %" GST_TIME_FORMAT, entry->pos + demux->ebml_segment_start,
+      entry->block, GST_TIME_ARGS (entry->time));
+
+  /* update the time */
+  g_assert (demux->src->len == demux->num_streams);
+  for (i = 0; i < demux->src->len; i++) {
+    GstMatroskaTrackContext *context = g_ptr_array_index (demux->src, i);
+    context->pos = entry->time;
+    context->set_discont = TRUE;
+    context->last_flow = GST_FLOW_OK;
+    context->eos = FALSE;
+    context->from_time = GST_CLOCK_TIME_NONE;
+  }
+  demux->segment.last_stop = entry->time;
+  demux->seek_block = entry->block;
+  demux->last_stop_end = GST_CLOCK_TIME_NONE;
+
+  if (reset) {
+    demux->from_offset = -1;
+    demux->to_offset = G_MAXINT64;
+  }
+
+  GST_OBJECT_UNLOCK (demux);
+
+  return TRUE;
+
+seek_error:
+  {
+    GST_OBJECT_UNLOCK (demux);
+    GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), ("Got a seek error"));
+    return FALSE;
+  }
+}
+
+static gboolean
 gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux,
     GstPad * pad, GstEvent * event)
 {
@@ -2166,7 +2227,6 @@ gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux,
   gboolean flush, keyunit;
   gdouble rate;
   gint64 cur, stop;
-  gint i;
   GstMatroskaTrackContext *track = NULL;
   GstSegment seeksegment = { 0, };
   gboolean update;
@@ -2185,12 +2245,6 @@ gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux,
     return FALSE;
   }
 
-  /* cannot yet do backwards playback */
-  if (rate <= 0.0) {
-    GST_DEBUG_OBJECT (demux, "Can only seek with positive rate");
-    return FALSE;
-  }
-
   /* copy segment, we need this because we still need the old
    * segment when we close the current segment. */
   memcpy (&seeksegment, &demux->segment, sizeof (GstSegment));
@@ -2205,9 +2259,9 @@ gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux,
 
   /* check sanity before we start flushing and all that */
   GST_OBJECT_LOCK (demux);
-  if ((entry =
-          gst_matroskademux_do_index_seek (demux, track,
-              seeksegment.last_stop, -1, FALSE)) == NULL) {
+  if ((entry = gst_matroskademux_do_index_seek (demux, track,
+              seeksegment.last_stop, &demux->seek_index, &demux->seek_entry)) ==
+      NULL) {
     GST_DEBUG_OBJECT (demux, "No matching seek entry in index");
     GST_OBJECT_UNLOCK (demux);
     return FALSE;
@@ -2233,19 +2287,6 @@ gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux,
   GST_DEBUG_OBJECT (demux, "Waiting for streaming to stop");
   GST_PAD_STREAM_LOCK (demux->sinkpad);
 
-  GST_OBJECT_LOCK (demux);
-
-  /* seek (relative to matroska segment) */
-  if (gst_ebml_read_seek (GST_EBML_READ (demux),
-          entry->pos + demux->ebml_segment_start) != GST_FLOW_OK) {
-    GST_DEBUG_OBJECT (demux, "Failed to seek to offset %" G_GUINT64_FORMAT,
-        entry->pos + demux->ebml_segment_start);
-    goto seek_error;
-  }
-
-  GST_DEBUG_OBJECT (demux, "Seeked to offset %" G_GUINT64_FORMAT, entry->pos +
-      demux->ebml_segment_start);
-
   if (keyunit) {
     GST_DEBUG_OBJECT (demux, "seek to key unit, adjusting segment start to %"
         GST_TIME_FORMAT, GST_TIME_ARGS (entry->time));
@@ -2254,8 +2295,6 @@ gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux,
     seeksegment.time = entry->time;
   }
 
-  GST_OBJECT_UNLOCK (demux);
-
   if (flush) {
     GST_DEBUG_OBJECT (demux, "Stopping flush");
     gst_pad_push_event (demux->sinkpad, gst_event_new_flush_stop ());
@@ -2279,6 +2318,10 @@ gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux,
   memcpy (&demux->segment, &seeksegment, sizeof (GstSegment));
   GST_OBJECT_UNLOCK (demux);
 
+  /* update some (segment) state */
+  if (!gst_matroska_demux_move_to_entry (demux, entry, TRUE))
+    goto seek_error;
+
   /* notify start of new segment */
   if (demux->segment.flags & GST_SEEK_FLAG_SEGMENT) {
     GstMessage *msg;
@@ -2293,22 +2336,9 @@ gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux,
     gst_event_unref (demux->new_segment);
   demux->new_segment = gst_event_new_new_segment_full (FALSE,
       demux->segment.rate, demux->segment.applied_rate, demux->segment.format,
-      demux->segment.last_stop, demux->segment.stop, demux->segment.time);
+      demux->segment.start, demux->segment.stop, demux->segment.time);
   GST_OBJECT_UNLOCK (demux);
 
-  /* update the time */
-  g_assert (demux->src->len == demux->num_streams);
-  for (i = 0; i < demux->src->len; i++) {
-    GstMatroskaTrackContext *context = g_ptr_array_index (demux->src, i);
-    context->pos = entry->time;
-    context->set_discont = TRUE;
-    context->last_flow = GST_FLOW_OK;
-    context->eos = FALSE;
-  }
-  demux->segment.last_stop = entry->time;
-  demux->seek_block = entry->block;
-  demux->last_stop_end = GST_CLOCK_TIME_NONE;
-
   /* restart our task since it might have been stopped when we did the
    * flush. */
   demux->segment_running = TRUE;
@@ -2360,6 +2390,55 @@ gst_matroska_demux_handle_src_event (GstPad * pad, GstEvent * event)
 }
 
 static GstFlowReturn
+gst_matroska_demux_seek_to_previous_keyframe (GstMatroskaDemux * demux)
+{
+  GstFlowReturn ret = GST_FLOW_UNEXPECTED;
+  gboolean done = TRUE;
+  gint i;
+
+  g_return_val_if_fail (demux->seek_index, GST_FLOW_UNEXPECTED);
+  g_return_val_if_fail (demux->seek_entry < demux->seek_index->len,
+      GST_FLOW_UNEXPECTED);
+
+  GST_DEBUG_OBJECT (demux, "locating previous keyframe");
+
+  if (!demux->seek_entry) {
+    GST_DEBUG_OBJECT (demux, "no earlier index entry");
+    goto exit;
+  }
+
+  for (i = 0; i < demux->src->len; i++) {
+    GstMatroskaTrackContext *stream = g_ptr_array_index (demux->src, i);
+
+    GST_DEBUG_OBJECT (demux, "segment start %" GST_TIME_FORMAT
+        ", stream %d at %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (demux->segment.start), stream->index,
+        GST_TIME_ARGS (stream->from_time));
+    if (GST_CLOCK_TIME_IS_VALID (stream->from_time)) {
+      if (stream->from_time > demux->segment.start) {
+        GST_DEBUG_OBJECT (demux, "stream %d not finished yet", stream->index);
+        done = FALSE;
+      }
+    }
+  }
+
+  if (!done) {
+    GstMatroskaIndex *entry;
+
+    entry = &g_array_index (demux->seek_index, GstMatroskaIndex,
+        --demux->seek_entry);
+    if (!gst_matroska_demux_move_to_entry (demux, entry, FALSE))
+      goto exit;
+    demux->to_offset = demux->from_offset;
+    demux->from_offset = -1;
+    ret = GST_FLOW_OK;
+  }
+
+exit:
+  return ret;
+}
+
+static GstFlowReturn
 gst_matroska_demux_parse_header (GstMatroskaDemux * demux)
 {
   GstEbmlRead *ebml = GST_EBML_READ (demux);
@@ -4099,6 +4178,9 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux,
   gint64 time = 0;
   gint flags = 0;
   gint64 referenceblock = 0;
+  gint64 offset;
+
+  offset = demux->parent.offset;
 
   while (ret == GST_FLOW_OK) {
     if (!is_simpleblock) {
@@ -4409,16 +4491,20 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux,
           GST_DEBUG_OBJECT (demux,
               "Stream %d after segment stop %" GST_TIME_FORMAT, stream->index,
               GST_TIME_ARGS (demux->segment.stop));
-          stream->eos = TRUE;
-          ret = GST_FLOW_OK;
-          /* combine flows */
-          ret = gst_matroska_demux_combine_flows (demux, stream, ret);
-          goto done;
+          gst_buffer_unref (sub);
+          goto eos;
+        }
+        if (offset >= demux->to_offset) {
+          GST_DEBUG_OBJECT (demux, "Stream %d after playback section",
+              stream->index);
+          gst_buffer_unref (sub);
+          goto eos;
         }
 
         /* handle gaps, e.g. non-zero start-time, or an cue index entry
          * that landed us with timestamps not quite intended */
-        if (GST_CLOCK_TIME_IS_VALID (demux->segment.last_stop)) {
+        if (GST_CLOCK_TIME_IS_VALID (demux->segment.last_stop) &&
+            demux->segment.rate > 0.0) {
           GstClockTimeDiff diff;
 
           /* only send newsegments with increasing start times,
@@ -4511,6 +4597,12 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux,
         stream->set_discont = FALSE;
       }
 
+      /* reverse playback book-keeping */
+      if (!GST_CLOCK_TIME_IS_VALID (stream->from_time))
+        stream->from_time = lace_time;
+      if (demux->from_offset == -1)
+        demux->from_offset = offset;
+
       GST_DEBUG_OBJECT (demux,
           "Pushing lace %d, data of size %d for stream %d, time=%"
           GST_TIME_FORMAT " and duration=%" GST_TIME_FORMAT, n,
@@ -4561,6 +4653,16 @@ done:
   g_free (lace_size);
 
   return ret;
+
+  /* EXITS */
+eos:
+  {
+    stream->eos = TRUE;
+    ret = GST_FLOW_OK;
+    /* combine flows */
+    ret = gst_matroska_demux_combine_flows (demux, stream, ret);
+    goto done;
+  }
 }
 
 /* return FALSE if block(group) should be skipped (due to a seek) */
@@ -5190,6 +5292,8 @@ gst_matroska_demux_loop (GstPad * pad)
   }
 
   ret = gst_matroska_demux_loop_stream (demux);
+  if (ret == GST_FLOW_UNEXPECTED)
+    goto eos;
   if (ret != GST_FLOW_OK)
     goto pause;
 
@@ -5208,7 +5312,7 @@ gst_matroska_demux_loop (GstPad * pad)
 
     GST_INFO_OBJECT (demux, "All streams are EOS");
     ret = GST_FLOW_UNEXPECTED;
-    goto pause;
+    goto eos;
   }
 
 next:
@@ -5221,6 +5325,15 @@ next:
   return;
 
   /* ERRORS */
+eos:
+  {
+    if (demux->segment.rate < 0.0) {
+      ret = gst_matroska_demux_seek_to_previous_keyframe (demux);
+      if (ret == GST_FLOW_OK)
+        goto next;
+    }
+    /* fall-through */
+  }
 pause:
   {
     const gchar *reason = gst_flow_get_name (ret);
index 9259eea..24d72ee 100644 (file)
@@ -105,6 +105,12 @@ typedef struct _GstMatroskaDemux {
   /* some state saving */
   GstClockTime             cluster_time;
   guint64                  cluster_offset;
+
+  /* reverse playback */
+  GArray                  *seek_index;
+  gint                     seek_entry;
+  gint64                   from_offset;
+  gint64                   to_offset;
 } GstMatroskaDemux;
 
 typedef struct _GstMatroskaDemuxClass {
index e35ea56..4c64875 100644 (file)
@@ -466,6 +466,8 @@ struct _GstMatroskaTrackContext {
   GstCaps      *caps;
   guint         index;
   GstFlowReturn last_flow;
+  /* reverse playback */
+  GstClockTime  from_time;
 
   GArray       *index_table;