gst/qtdemux/qtdemux.c: Implement reverse playback support.

Original commit message from CVS:
2007-11-24  Julien MOUTTE  <julien@moutte.net>

* gst/qtdemux/qtdemux.c: (gst_qtdemux_find_segment),
(gst_qtdemux_move_stream), (gst_qtdemux_do_seek),
(gst_qtdemux_seek_to_previous_keyframe),
(gst_qtdemux_activate_segment), (gst_qtdemux_advance_sample),
(gst_qtdemux_loop_state_movie), (gst_qtdemux_loop): Implement
reverse playback support.
This commit is contained in:
Julien Moutte 2007-11-24 14:55:04 +00:00
parent f04ee6e996
commit 848829798a
2 changed files with 223 additions and 31 deletions

View file

@ -1,3 +1,12 @@
2007-11-24 Julien MOUTTE <julien@moutte.net>
* gst/qtdemux/qtdemux.c: (gst_qtdemux_find_segment),
(gst_qtdemux_move_stream), (gst_qtdemux_do_seek),
(gst_qtdemux_seek_to_previous_keyframe),
(gst_qtdemux_activate_segment), (gst_qtdemux_advance_sample),
(gst_qtdemux_loop_state_movie), (gst_qtdemux_loop): Implement
reverse playback support.
2007-11-20 Sebastian Dröge <slomo@circular-chaos.org> 2007-11-20 Sebastian Dröge <slomo@circular-chaos.org>
* ext/wavpack/gstwavpackcommon.c: (gst_wavpack_set_channel_layout): * ext/wavpack/gstwavpackcommon.c: (gst_wavpack_set_channel_layout):

View file

@ -2,6 +2,7 @@
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
* Copyright (C) <2003> David A. Schleef <ds@schleef.org> * Copyright (C) <2003> David A. Schleef <ds@schleef.org>
* Copyright (C) <2006> Wim Taymans <wim@fluendo.com> * Copyright (C) <2006> Wim Taymans <wim@fluendo.com>
* Copyright (C) <2007> Julien Moutte <julien@fluendo.com>
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public * modify it under the terms of the GNU Library General Public
@ -175,7 +176,8 @@ struct _QtDemuxStream
/* quicktime segments */ /* quicktime segments */
guint32 n_segments; guint32 n_segments;
QtDemuxSegment *segments; QtDemuxSegment *segments;
gboolean segment_pending; guint32 from_sample;
guint32 to_sample;
}; };
enum QtDemuxState enum QtDemuxState
@ -563,7 +565,7 @@ gst_qtdemux_find_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
for (i = 0; i < stream->n_segments; i++) { for (i = 0; i < stream->n_segments; i++) {
QtDemuxSegment *segment = &stream->segments[i]; QtDemuxSegment *segment = &stream->segments[i];
if (segment->time <= time_position && time_position < segment->stop_time) { if (segment->time <= time_position && time_position <= segment->stop_time) {
seg_idx = i; seg_idx = i;
break; break;
} }
@ -588,6 +590,9 @@ gst_qtdemux_move_stream (GstQTDemux * qtdemux, QtDemuxStream * str,
/* position changed, we have a discont */ /* position changed, we have a discont */
str->sample_index = index; str->sample_index = index;
/* Each time we move in the stream we store the position where we are
* starting from */
str->from_sample = index;
str->discont = TRUE; str->discont = TRUE;
} }
@ -793,13 +798,25 @@ gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
" to %" G_GINT64_FORMAT, qtdemux->segment.start, " to %" G_GINT64_FORMAT, qtdemux->segment.start,
qtdemux->segment.last_stop); qtdemux->segment.last_stop);
/* FIXME, needs to be done from the streaming thread. Also, the rate is the if (qtdemux->segment.rate >= 0) {
* product of the global rate and the (quicktime) segment rate. */ /* FIXME, needs to be done from the streaming thread. Also, the rate is the
gst_qtdemux_push_event (qtdemux, * product of the global rate and the (quicktime) segment rate. */
gst_event_new_new_segment (TRUE, gst_qtdemux_push_event (qtdemux,
qtdemux->segment.rate, qtdemux->segment.format, gst_event_new_new_segment (TRUE,
qtdemux->segment.start, qtdemux->segment.last_stop, qtdemux->segment.rate, qtdemux->segment.format,
qtdemux->segment.time)); qtdemux->segment.start, qtdemux->segment.last_stop,
qtdemux->segment.time));
} else { /* For Reverse Playback */
guint64 stop;
if ((stop = qtdemux->segment.stop) == -1)
stop = qtdemux->segment.duration;
/* for reverse playback, we played from stop to last_stop. */
gst_qtdemux_push_event (qtdemux,
gst_event_new_new_segment (TRUE,
qtdemux->segment.rate, qtdemux->segment.format,
qtdemux->segment.last_stop, stop, qtdemux->segment.last_stop));
}
} }
/* commit the new segment */ /* commit the new segment */
@ -1045,6 +1062,126 @@ beach:
return ret; return ret;
} }
/* Seeks to the previous keyframe of the indexed stream and
* aligns other streams with respect to the keyframe timestamp
* of indexed stream. Only called in case of Reverse Playback
*/
static GstFlowReturn
gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux)
{
guint8 n = 0;
guint32 seg_idx = 0, index = 0, kindex = 0;
guint64 desired_offset = 0, last_stop = 0, media_start = 0, seg_time = 0;
QtDemuxSegment *seg = NULL;
QtDemuxStream *ref_str = NULL, *str = NULL;
/* Now we choose an arbitrary stream, get the previous keyframe timestamp
* and finally align all the other streams on that timestamp with their
* respective keyframes */
for (n = 0; n < qtdemux->n_streams; n++) {
str = qtdemux->streams[n];
seg_idx = gst_qtdemux_find_segment (qtdemux, str,
qtdemux->segment.last_stop);
/* segment not found, continue with normal flow */
if (seg_idx == -1)
continue;
/* No candidate yet, take that one */
if (!ref_str) {
ref_str = str;
continue;
}
/* So that stream has a segment, we prefer video streams */
if (str->subtype == FOURCC_vide) {
ref_str = str;
break;
}
}
if (G_UNLIKELY (!ref_str)) {
GST_WARNING_OBJECT (qtdemux, "couldn't find any stream");
return GST_FLOW_ERROR;
}
if (G_UNLIKELY (!ref_str->from_sample)) {
GST_DEBUG_OBJECT (qtdemux, "reached the beginning of the file");
return GST_FLOW_UNEXPECTED;
}
/* So that stream has been playing from from_sample to to_sample. We will
* get the timestamp of the previous sample and search for a keyframe before
* that. For audio streams we do an arbitrary jump in the past (10 samples) */
if (ref_str->subtype == FOURCC_vide) {
kindex = gst_qtdemux_find_keyframe (qtdemux, ref_str,
ref_str->from_sample - 1);
} else {
kindex = ref_str->from_sample - 10;
}
desired_offset = ref_str->samples[kindex].timestamp;
last_stop = ref_str->samples[ref_str->from_sample].timestamp;
/* Bring that back to global time */
seg = &ref_str->segments[seg_idx];
/* Sample global timestamp is timestamp - seg_start + seg_time */
desired_offset = (desired_offset - seg->media_start) + seg->time;
last_stop = (last_stop - seg->media_start) + seg->time;
GST_DEBUG_OBJECT (qtdemux, "preferred stream played from sample %u, "
"now going to sample %u (pts %" GST_TIME_FORMAT ")", ref_str->from_sample,
kindex, GST_TIME_ARGS (desired_offset));
/* Set last_stop with the keyframe timestamp we pushed of that stream */
gst_segment_set_last_stop (&qtdemux->segment, GST_FORMAT_TIME, last_stop);
GST_DEBUG_OBJECT (qtdemux, "last_stop now is %" GST_TIME_FORMAT,
GST_TIME_ARGS (last_stop));
if (G_UNLIKELY (last_stop < qtdemux->segment.start)) {
GST_DEBUG_OBJECT (qtdemux, "reached the beginning of segment");
return GST_FLOW_UNEXPECTED;
}
/* Align them all on this */
for (n = 0; n < qtdemux->n_streams; n++) {
str = qtdemux->streams[n];
seg_idx = gst_qtdemux_find_segment (qtdemux, str, desired_offset);
GST_DEBUG_OBJECT (qtdemux, "align segment %d", seg_idx);
/* segment not found, continue with normal flow */
if (seg_idx == -1)
continue;
/* get segment and time in the segment */
seg = &str->segments[seg_idx];
seg_time = desired_offset - seg->time;
/* get the media time in the segment */
media_start = seg->media_start + seg_time;
/* get the index of the sample with media time */
index = gst_qtdemux_find_index (qtdemux, str, media_start);
GST_DEBUG_OBJECT (qtdemux, "sample for %" GST_TIME_FORMAT " at %u",
GST_TIME_ARGS (media_start), index);
/* find previous keyframe */
kindex = gst_qtdemux_find_keyframe (qtdemux, str, index);
/* Remember until where we want to go */
str->to_sample = str->from_sample - 1;
/* Define our time position */
str->time_position =
(str->samples[kindex].timestamp - seg->media_start) + seg->time;
/* Now seek back in time */
gst_qtdemux_move_stream (qtdemux, str, kindex);
GST_DEBUG_OBJECT (qtdemux, "keyframe at %u, time position %"
GST_TIME_FORMAT " playing from sample %u to %u", kindex,
GST_TIME_ARGS (str->time_position), str->from_sample, str->to_sample);
}
return GST_FLOW_OK;
}
/* activate the given segment number @seg_idx of @stream at time @offset. /* activate the given segment number @seg_idx of @stream at time @offset.
* @offset is an absolute global position over all the segments. * @offset is an absolute global position over all the segments.
* *
@ -1060,7 +1197,7 @@ gst_qtdemux_activate_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
QtDemuxSegment *segment; QtDemuxSegment *segment;
guint32 index, kf_index; guint32 index, kf_index;
guint64 seg_time; guint64 seg_time;
guint64 start, stop; guint64 start, stop, time;
gdouble rate; gdouble rate;
/* update the current segment */ /* update the current segment */
@ -1075,7 +1212,7 @@ gst_qtdemux_activate_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
/* get time in this segment */ /* get time in this segment */
seg_time = offset - segment->time; seg_time = offset - segment->time;
if (seg_time >= segment->duration) if (seg_time > segment->duration)
return FALSE; return FALSE;
/* calc media start/stop */ /* calc media start/stop */
@ -1083,11 +1220,18 @@ gst_qtdemux_activate_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
stop = segment->media_stop; stop = segment->media_stop;
else else
stop = MIN (segment->media_stop, qtdemux->segment.stop); stop = MIN (segment->media_stop, qtdemux->segment.stop);
start = MIN (segment->media_start + seg_time, stop); if (qtdemux->segment.rate >= 0) {
start = MIN (segment->media_start + seg_time, stop);
time = offset;
} else {
start = segment->media_start;
stop = MIN (segment->media_start + seg_time, stop);
time = segment->media_start;
}
GST_DEBUG_OBJECT (qtdemux, "newsegment %d from %" GST_TIME_FORMAT GST_DEBUG_OBJECT (qtdemux, "newsegment %d from %" GST_TIME_FORMAT
" to %" GST_TIME_FORMAT ", time %" GST_TIME_FORMAT, seg_idx, " to %" GST_TIME_FORMAT ", time %" GST_TIME_FORMAT, seg_idx,
GST_TIME_ARGS (start), GST_TIME_ARGS (stop), GST_TIME_ARGS (offset)); GST_TIME_ARGS (start), GST_TIME_ARGS (stop), GST_TIME_ARGS (time));
/* combine global rate with that of the segment */ /* combine global rate with that of the segment */
rate = segment->rate * qtdemux->segment.rate; rate = segment->rate * qtdemux->segment.rate;
@ -1095,12 +1239,12 @@ gst_qtdemux_activate_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
/* update the segment values used for clipping */ /* update the segment values used for clipping */
gst_segment_init (&stream->segment, GST_FORMAT_TIME); gst_segment_init (&stream->segment, GST_FORMAT_TIME);
gst_segment_set_newsegment (&stream->segment, FALSE, rate, GST_FORMAT_TIME, gst_segment_set_newsegment (&stream->segment, FALSE, rate, GST_FORMAT_TIME,
start, stop, offset); start, stop, time);
/* now prepare and send the segment */ /* now prepare and send the segment */
if (stream->pad) { if (stream->pad) {
event = gst_event_new_new_segment (FALSE, rate, GST_FORMAT_TIME, event = gst_event_new_new_segment (FALSE, rate, GST_FORMAT_TIME,
start, stop, offset); start, stop, time);
gst_pad_push_event (stream->pad, event); gst_pad_push_event (stream->pad, event);
/* assume we can send more data now */ /* assume we can send more data now */
stream->last_ret = GST_FLOW_OK; stream->last_ret = GST_FLOW_OK;
@ -1108,10 +1252,19 @@ gst_qtdemux_activate_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
/* and move to the keyframe before the indicated media time of the /* and move to the keyframe before the indicated media time of the
* segment */ * segment */
index = gst_qtdemux_find_index (qtdemux, stream, start); if (qtdemux->segment.rate >= 0) {
index = gst_qtdemux_find_index (qtdemux, stream, start);
GST_DEBUG_OBJECT (qtdemux, "moving data pointer to %" GST_TIME_FORMAT stream->to_sample = stream->n_samples;
", index: %u", GST_TIME_ARGS (start), index); GST_DEBUG_OBJECT (qtdemux, "moving data pointer to %" GST_TIME_FORMAT
", index: %u, pts %" GST_TIME_FORMAT, GST_TIME_ARGS (start), index,
GST_TIME_ARGS (stream->samples[index].timestamp));
} else {
index = gst_qtdemux_find_index (qtdemux, stream, stop);
stream->to_sample = index;
GST_DEBUG_OBJECT (qtdemux, "moving data pointer to %" GST_TIME_FORMAT
", index: %u, pts %" GST_TIME_FORMAT, GST_TIME_ARGS (stop), index,
GST_TIME_ARGS (stream->samples[index].timestamp));
}
/* we're at the right spot */ /* we're at the right spot */
if (index == stream->sample_index) if (index == stream->sample_index)
@ -1126,14 +1279,19 @@ gst_qtdemux_activate_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
if (index > stream->sample_index) { if (index > stream->sample_index) {
/* moving forwards check if we move past a keyframe */ /* moving forwards check if we move past a keyframe */
if (kf_index > stream->sample_index) { if (kf_index > stream->sample_index) {
GST_DEBUG_OBJECT (qtdemux, "moving forwards to keyframe at %u", kf_index); GST_DEBUG_OBJECT (qtdemux, "moving forwards to keyframe at %u (pts %"
GST_TIME_FORMAT, kf_index,
GST_TIME_ARGS (stream->samples[kf_index].timestamp));
gst_qtdemux_move_stream (qtdemux, stream, kf_index); gst_qtdemux_move_stream (qtdemux, stream, kf_index);
} else { } else {
GST_DEBUG_OBJECT (qtdemux, "moving forwards, keyframe at %u already sent", GST_DEBUG_OBJECT (qtdemux, "moving forwards, keyframe at %u (pts %"
kf_index); GST_TIME_FORMAT " already sent", kf_index,
GST_TIME_ARGS (stream->samples[kf_index].timestamp));
} }
} else { } else {
GST_DEBUG_OBJECT (qtdemux, "moving backwards to keyframe at %u", kf_index); GST_DEBUG_OBJECT (qtdemux, "moving backwards to keyframe at %u (pts %"
GST_TIME_FORMAT, kf_index,
GST_TIME_ARGS (stream->samples[kf_index].timestamp));
gst_qtdemux_move_stream (qtdemux, stream, kf_index); gst_qtdemux_move_stream (qtdemux, stream, kf_index);
} }
@ -1232,6 +1390,14 @@ gst_qtdemux_advance_sample (GstQTDemux * qtdemux, QtDemuxStream * stream)
QtDemuxSample *sample; QtDemuxSample *sample;
QtDemuxSegment *segment; QtDemuxSegment *segment;
if (stream->sample_index >= stream->to_sample) {
/* Mark the stream as EOS */
GST_DEBUG_OBJECT (qtdemux, "reached max allowed sample %u, mark EOS",
stream->to_sample);
stream->time_position = -1;
return;
}
/* move to next sample */ /* move to next sample */
stream->sample_index++; stream->sample_index++;
@ -1461,12 +1627,16 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
} }
} }
/* all are EOS */ /* all are EOS */
if (index == -1) if (index == -1) {
GST_DEBUG_OBJECT (qtdemux, "all streams are EOS");
goto eos; goto eos;
}
/* check for segment end */ /* check for segment end */
if (qtdemux->segment.stop != -1 && qtdemux->segment.stop < min_time) if (qtdemux->segment.stop != -1 && qtdemux->segment.stop < min_time) {
GST_DEBUG_OBJECT (qtdemux, "we reached the end of our segment.");
goto eos; goto eos;
}
stream = qtdemux->streams[index]; stream = qtdemux->streams[index];
@ -1511,8 +1681,9 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
} }
qtdemux->last_ts = min_time; qtdemux->last_ts = min_time;
gst_segment_set_last_stop (&qtdemux->segment, GST_FORMAT_TIME, min_time); if (qtdemux->segment.rate >= 0) {
gst_segment_set_last_stop (&qtdemux->segment, GST_FORMAT_TIME, min_time);
}
if (stream->pad) { if (stream->pad) {
/* we're going to modify the metadata */ /* we're going to modify the metadata */
buf = gst_buffer_make_metadata_writable (buf); buf = gst_buffer_make_metadata_writable (buf);
@ -1593,6 +1764,9 @@ gst_qtdemux_loop (GstPad * pad)
break; break;
case QTDEMUX_STATE_MOVIE: case QTDEMUX_STATE_MOVIE:
ret = gst_qtdemux_loop_state_movie (qtdemux); ret = gst_qtdemux_loop_state_movie (qtdemux);
if (qtdemux->segment.rate < 0 && ret == GST_FLOW_UNEXPECTED) {
ret = gst_qtdemux_seek_to_previous_keyframe (qtdemux);
}
break; break;
default: default:
/* ouch */ /* ouch */
@ -1642,10 +1816,19 @@ pause:
if ((stop = qtdemux->segment.stop) == -1) if ((stop = qtdemux->segment.stop) == -1)
stop = qtdemux->segment.duration; stop = qtdemux->segment.duration;
GST_LOG_OBJECT (qtdemux, "Sending segment done, at end of segment"); if (qtdemux->segment.rate >= 0) {
gst_element_post_message (GST_ELEMENT_CAST (qtdemux), GST_LOG_OBJECT (qtdemux, "Sending segment done, at end of segment");
gst_message_new_segment_done (GST_OBJECT_CAST (qtdemux), gst_element_post_message (GST_ELEMENT_CAST (qtdemux),
GST_FORMAT_TIME, stop)); gst_message_new_segment_done (GST_OBJECT_CAST (qtdemux),
GST_FORMAT_TIME, stop));
} else {
/* For Reverse Playback */
GST_LOG_OBJECT (qtdemux,
"Sending segment done, at start of segment");
gst_element_post_message (GST_ELEMENT_CAST (qtdemux),
gst_message_new_segment_done (GST_OBJECT_CAST (qtdemux),
GST_FORMAT_TIME, qtdemux->segment.start));
}
} else { } else {
GST_LOG_OBJECT (qtdemux, "Sending EOS at end of segment"); GST_LOG_OBJECT (qtdemux, "Sending EOS at end of segment");
gst_qtdemux_push_event (qtdemux, gst_event_new_eos ()); gst_qtdemux_push_event (qtdemux, gst_event_new_eos ());