From 1c7b1d110bcdfff54bb090cbeeabd0d2a501da86 Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Wed, 31 Mar 2010 14:40:50 +0200 Subject: [PATCH] flvdemux: support (pull mode) negative seek rate --- gst/flv/gstflvdemux.c | 129 ++++++++++++++++++++++++++++++++++++++++---------- gst/flv/gstflvdemux.h | 8 ++++ gst/flv/gstflvparse.c | 50 ++++++++++++++----- 3 files changed, 151 insertions(+), 36 deletions(-) diff --git a/gst/flv/gstflvdemux.c b/gst/flv/gstflvdemux.c index 09b1272..bab9b65 100644 --- a/gst/flv/gstflvdemux.c +++ b/gst/flv/gstflvdemux.c @@ -565,10 +565,80 @@ beach: return ret; } +static void +gst_flv_demux_move_to_offset (GstFLVDemux * demux, gint64 offset, + gboolean reset) +{ + demux->offset = offset; + + /* Tell all the stream we moved to a different position (discont) */ + demux->audio_need_discont = TRUE; + demux->video_need_discont = TRUE; + + /* next section setup */ + demux->from_offset = -1; + demux->audio_done = demux->video_done = FALSE; + demux->audio_first_ts = demux->video_first_ts = GST_CLOCK_TIME_NONE; + + if (reset) { + demux->from_offset = -1; + demux->to_offset = G_MAXINT64; + } + + /* If we seeked at the beginning of the file parse the header again */ + if (G_UNLIKELY (!demux->offset)) { + demux->state = FLV_STATE_HEADER; + } else { /* or parse a tag */ + demux->state = FLV_STATE_TAG_TYPE; + } +} + static GstFlowReturn gst_flv_demux_seek_to_prev_keyframe (GstFLVDemux * demux) { - return GST_FLOW_OK; + GstFlowReturn ret = GST_FLOW_UNEXPECTED; + GstIndexEntry *entry = NULL; + + GST_DEBUG_OBJECT (demux, + "terminated section started at offset %" G_GINT64_FORMAT, + demux->from_offset); + + /* we are done if we got all audio and video */ + if ((!GST_CLOCK_TIME_IS_VALID (demux->audio_first_ts) || + demux->audio_first_ts < demux->segment.start) && + (!GST_CLOCK_TIME_IS_VALID (demux->video_first_ts) || + demux->video_first_ts < demux->segment.start)) + goto done; + + if (demux->from_offset <= 0) + goto done; + + GST_DEBUG_OBJECT (demux, "locating previous position"); + + /* locate index entry before previous start position */ + if (demux->index) + entry = gst_index_get_assoc_entry (demux->index, demux->index_id, + GST_INDEX_LOOKUP_BEFORE, GST_ASSOCIATION_FLAG_KEY_UNIT, + GST_FORMAT_BYTES, demux->from_offset - 1); + + if (entry) { + gint64 bytes, time; + + gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &bytes); + gst_index_entry_assoc_map (entry, GST_FORMAT_TIME, &time); + + GST_DEBUG_OBJECT (demux, "found index entry for %" G_GINT64_FORMAT + " at %" GST_TIME_FORMAT ", seeking to %" G_GINT64_FORMAT, + demux->offset - 1, GST_TIME_ARGS (time), bytes); + + /* setup for next section */ + demux->to_offset = demux->from_offset; + gst_flv_demux_move_to_offset (demux, bytes, FALSE); + ret = GST_FLOW_OK; + } + +done: + return ret; } static gboolean @@ -702,10 +772,12 @@ gst_flv_demux_loop (GstPad * pad) demux = GST_FLV_DEMUX (gst_pad_get_parent (pad)); - if (demux->segment.rate >= 0) { + if (TRUE || demux->segment.rate >= 0) { /* pull in data */ switch (demux->state) { case FLV_STATE_TAG_TYPE: + if (demux->from_offset == -1) + demux->from_offset = demux->offset; ret = gst_flv_demux_pull_tag (pad, demux); /* if we have seen real data, we probably passed a possible metadata * header located at start. So if we do not yet have an index, @@ -738,16 +810,23 @@ gst_flv_demux_loop (GstPad * pad) break; } - /* pause if something went wrong */ - if (G_UNLIKELY (ret != GST_FLOW_OK)) - goto pause; + if (demux->segment.rate < 0.0) { + /* check end of section */ + if ((gint64) demux->offset >= demux->to_offset || + demux->segment.last_stop >= demux->segment.stop + 2 * GST_SECOND || + (demux->audio_done && demux->video_done)) + ret = gst_flv_demux_seek_to_prev_keyframe (demux); + } else { + /* check EOS condition */ + if ((demux->segment.stop != -1) && + (demux->segment.last_stop >= demux->segment.stop)) { + ret = GST_FLOW_UNEXPECTED; + } + } - /* check EOS condition */ - if ((demux->segment.stop != -1) && - (demux->segment.last_stop >= demux->segment.stop)) { - ret = GST_FLOW_UNEXPECTED; + /* pause if something went wrong or at end */ + if (G_UNLIKELY (ret != GST_FLOW_OK)) goto pause; - } } else { /* Reverse playback */ /* pull in data */ switch (demux->state) { @@ -849,7 +928,7 @@ gst_flv_demux_find_offset (GstFLVDemux * demux, GstSegment * segment) g_return_val_if_fail (segment != NULL, 0); - time = segment->start; + time = segment->last_stop; if (demux->index) { /* Let's check if we have an index entry for that seek time */ @@ -863,7 +942,7 @@ gst_flv_demux_find_offset (GstFLVDemux * demux, GstSegment * segment) GST_DEBUG_OBJECT (demux, "found index entry for %" GST_TIME_FORMAT " at %" GST_TIME_FORMAT ", seeking to %" G_GINT64_FORMAT, - GST_TIME_ARGS (segment->start), GST_TIME_ARGS (time), bytes); + GST_TIME_ARGS (segment->last_stop), GST_TIME_ARGS (time), bytes); /* Key frame seeking */ if (segment->flags & GST_SEEK_FLAG_KEY_UNIT) { @@ -1103,18 +1182,8 @@ gst_flv_demux_handle_seek_pull (GstFLVDemux * demux, GstEvent * event, goto exit; } /* now index should be as reliable as it can be for current purpose */ - demux->offset = gst_flv_demux_find_offset (demux, &seeksegment); - - /* Tell all the stream we moved to a different position (discont) */ - demux->audio_need_discont = TRUE; - demux->video_need_discont = TRUE; - - /* If we seeked at the beginning of the file parse the header again */ - if (G_UNLIKELY (!demux->offset)) { - demux->state = FLV_STATE_HEADER; - } else { /* or parse a tag */ - demux->state = FLV_STATE_TAG_TYPE; - } + gst_flv_demux_move_to_offset (demux, + gst_flv_demux_find_offset (demux, &seeksegment), TRUE); ret = TRUE; } else { ret = TRUE; @@ -1171,6 +1240,18 @@ gst_flv_demux_handle_seek_pull (GstFLVDemux * demux, GstEvent * event, gst_event_unref (demux->new_seg_event); demux->new_seg_event = NULL; } + if (demux->segment.rate < 0.0) { + /* we can't generate a segment by locking on + * to the first timestamp we see */ + GST_DEBUG_OBJECT (demux, "preparing newsegment from %" + GST_TIME_FORMAT " to %" GST_TIME_FORMAT, + GST_TIME_ARGS (demux->segment.start), + GST_TIME_ARGS (demux->segment.stop)); + demux->new_seg_event = + gst_event_new_new_segment (FALSE, demux->segment.rate, + demux->segment.format, demux->segment.start, + demux->segment.stop, demux->segment.start); + } } exit: diff --git a/gst/flv/gstflvdemux.h b/gst/flv/gstflvdemux.h index 31da16b..4d51cad 100644 --- a/gst/flv/gstflvdemux.h +++ b/gst/flv/gstflvdemux.h @@ -129,6 +129,14 @@ struct _GstFLVDemux GstClockTime index_max_time; gint64 index_max_pos; + + /* reverse playback */ + GstClockTime video_first_ts; + GstClockTime audio_first_ts; + gboolean video_done; + gboolean audio_done; + gint64 from_offset; + gint64 to_offset; }; struct _GstFLVDemuxClass diff --git a/gst/flv/gstflvparse.c b/gst/flv/gstflvparse.c index 66e52c4..a9a0b5b 100644 --- a/gst/flv/gstflvparse.c +++ b/gst/flv/gstflvparse.c @@ -856,6 +856,9 @@ gst_flv_parse_tag_audio (GstFLVDemux * demux, GstBuffer * buffer) if (!GST_CLOCK_TIME_IS_VALID (demux->audio_start)) { demux->audio_start = GST_BUFFER_TIMESTAMP (outbuf); } + if (!GST_CLOCK_TIME_IS_VALID (demux->audio_first_ts)) { + demux->audio_first_ts = GST_BUFFER_TIMESTAMP (outbuf); + } if (G_UNLIKELY (!demux->no_more_pads && (GST_CLOCK_DIFF (demux->audio_start, @@ -870,13 +873,23 @@ gst_flv_parse_tag_audio (GstFLVDemux * demux, GstBuffer * buffer) /* Push downstream */ ret = gst_pad_push (demux->audio_pad, outbuf); if (G_UNLIKELY (ret != GST_FLOW_OK)) { - GST_WARNING_OBJECT (demux, "failed pushing a %" G_GUINT64_FORMAT - " bytes audio buffer: %s", demux->tag_data_size, - gst_flow_get_name (ret)); - if (ret == GST_FLOW_NOT_LINKED) { - demux->audio_linked = FALSE; + if (demux->segment.rate < 0.0 && ret == GST_FLOW_UNEXPECTED && + demux->segment.last_stop > demux->segment.stop) { + /* In reverse playback we can get a GST_FLOW_UNEXPECTED when + * we are at the end of the segment, so we just need to jump + * back to the previous section. */ + GST_DEBUG_OBJECT (demux, "downstream has reached end of segment"); + demux->audio_done = TRUE; + ret = GST_FLOW_OK; + } else { + GST_WARNING_OBJECT (demux, "failed pushing a %" G_GUINT64_FORMAT + " bytes audio buffer: %s", demux->tag_data_size, + gst_flow_get_name (ret)); + if (ret == GST_FLOW_NOT_LINKED) { + demux->audio_linked = FALSE; + } + goto beach; } - goto beach; } demux->audio_linked = TRUE; @@ -1192,6 +1205,9 @@ gst_flv_parse_tag_video (GstFLVDemux * demux, GstBuffer * buffer) if (!GST_CLOCK_TIME_IS_VALID (demux->video_start)) { demux->video_start = GST_BUFFER_TIMESTAMP (outbuf); } + if (!GST_CLOCK_TIME_IS_VALID (demux->audio_first_ts)) { + demux->video_first_ts = GST_BUFFER_TIMESTAMP (outbuf); + } if (G_UNLIKELY (!demux->no_more_pads && (GST_CLOCK_DIFF (demux->video_start, @@ -1207,13 +1223,23 @@ gst_flv_parse_tag_video (GstFLVDemux * demux, GstBuffer * buffer) ret = gst_pad_push (demux->video_pad, outbuf); if (G_UNLIKELY (ret != GST_FLOW_OK)) { - GST_WARNING_OBJECT (demux, "failed pushing a %" G_GUINT64_FORMAT - " bytes video buffer: %s", demux->tag_data_size, - gst_flow_get_name (ret)); - if (ret == GST_FLOW_NOT_LINKED) { - demux->video_linked = FALSE; + if (demux->segment.rate < 0.0 && ret == GST_FLOW_UNEXPECTED && + demux->segment.last_stop > demux->segment.stop) { + /* In reverse playback we can get a GST_FLOW_UNEXPECTED when + * we are at the end of the segment, so we just need to jump + * back to the previous section. */ + GST_DEBUG_OBJECT (demux, "downstream has reached end of segment"); + demux->video_done = TRUE; + ret = GST_FLOW_OK; + } else { + GST_WARNING_OBJECT (demux, "failed pushing a %" G_GUINT64_FORMAT + " bytes video buffer: %s", demux->tag_data_size, + gst_flow_get_name (ret)); + if (ret == GST_FLOW_NOT_LINKED) { + demux->video_linked = FALSE; + } + goto beach; } - goto beach; } demux->video_linked = TRUE; -- 2.7.4