summaryrefslogtreecommitdiffstats
path: root/gst/qtdemux
diff options
context:
space:
mode:
Diffstat (limited to 'gst/qtdemux')
-rw-r--r--gst/qtdemux/qtdemux.c416
1 files changed, 334 insertions, 82 deletions
diff --git a/gst/qtdemux/qtdemux.c b/gst/qtdemux/qtdemux.c
index d1ddef5a..ae69e3f3 100644
--- a/gst/qtdemux/qtdemux.c
+++ b/gst/qtdemux/qtdemux.c
@@ -484,6 +484,8 @@ gst_qtdemux_handle_src_query (GstPad * pad, GstQuery * query)
gboolean res = FALSE;
GstQTDemux *qtdemux = GST_QTDEMUX (gst_pad_get_parent (pad));
+ GST_LOG_OBJECT (pad, "%s query", GST_QUERY_TYPE_NAME (query));
+
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_POSITION:
if (GST_CLOCK_TIME_IS_VALID (qtdemux->segment.last_stop)) {
@@ -509,14 +511,27 @@ gst_qtdemux_handle_src_query (GstPad * pad, GstQuery * query)
}
case GST_QUERY_SEEKING:{
GstFormat fmt;
+ gboolean seekable;
gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL);
if (fmt == GST_FORMAT_TIME) {
gint64 duration = -1;
gst_qtdemux_get_duration (qtdemux, &duration);
- gst_query_set_seeking (query, GST_FORMAT_TIME, qtdemux->pullbased,
- 0, duration);
+ seekable = TRUE;
+ if (!qtdemux->pullbased) {
+ GstQuery *q;
+
+ /* we might be able with help from upstream */
+ seekable = FALSE;
+ q = gst_query_new_seeking (GST_FORMAT_BYTES);
+ if ((res = gst_pad_peer_query (qtdemux->sinkpad, q))) {
+ gst_query_parse_seeking (q, &fmt, &seekable, NULL, NULL);
+ GST_LOG_OBJECT (qtdemux, "upstream BYTE seekable %d", seekable);
+ }
+ gst_query_unref (q);
+ }
+ gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, 0, duration);
res = TRUE;
}
break;
@@ -688,6 +703,177 @@ gst_qtdemux_move_stream (GstQTDemux * qtdemux, QtDemuxStream * str,
str->discont = TRUE;
}
+static void
+gst_qtdemux_adjust_seek (GstQTDemux * qtdemux, gint64 desired_time,
+ gint64 * key_time, gint64 * key_offset)
+{
+ guint64 min_offset;
+ gint64 min_byte_offset = -1;
+ gint n;
+
+ min_offset = desired_time;
+
+ /* for each stream, find the index of the sample in the segment
+ * and move back to the previous keyframe. */
+ for (n = 0; n < qtdemux->n_streams; n++) {
+ QtDemuxStream *str;
+ guint32 index, kindex;
+ guint32 seg_idx;
+ guint64 media_start;
+ guint64 media_time;
+ guint64 seg_time;
+ QtDemuxSegment *seg;
+
+ str = qtdemux->streams[n];
+
+ seg_idx = gst_qtdemux_find_segment (qtdemux, str, desired_time);
+ 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_time - 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);
+
+ /* if the keyframe is at a different position, we need to update the
+ * requested seek time */
+ if (index != kindex) {
+ index = kindex;
+
+ /* get timestamp of keyframe */
+ media_time = str->samples[kindex].timestamp;
+ GST_DEBUG_OBJECT (qtdemux, "keyframe at %u with time %" GST_TIME_FORMAT,
+ kindex, GST_TIME_ARGS (media_time));
+
+ /* keyframes in the segment get a chance to change the
+ * desired_offset. keyframes out of the segment are
+ * ignored. */
+ if (media_time >= seg->media_start) {
+ guint64 seg_time;
+
+ /* this keyframe is inside the segment, convert back to
+ * segment time */
+ seg_time = (media_time - seg->media_start) + seg->time;
+ if (seg_time < min_offset)
+ min_offset = seg_time;
+ }
+ }
+
+ if (min_byte_offset < 0 || str->samples[index].offset < min_byte_offset)
+ min_byte_offset = str->samples[index].offset;
+ }
+
+ if (key_time)
+ *key_time = min_offset;
+ if (key_offset)
+ *key_offset = min_byte_offset;
+}
+
+static gboolean
+gst_qtdemux_convert_seek (GstPad * pad, GstFormat * format,
+ GstSeekType cur_type, gint64 * cur, GstSeekType stop_type, gint64 * stop)
+{
+ gboolean res;
+ GstFormat fmt;
+
+ g_return_val_if_fail (format != NULL, FALSE);
+ g_return_val_if_fail (cur != NULL, FALSE);
+ g_return_val_if_fail (stop != NULL, FALSE);
+
+ if (*format == GST_FORMAT_TIME)
+ return TRUE;
+
+ fmt = GST_FORMAT_TIME;
+ res = TRUE;
+ if (cur_type != GST_SEEK_TYPE_NONE)
+ res = gst_pad_query_convert (pad, *format, *cur, &fmt, cur);
+ if (res && stop_type != GST_SEEK_TYPE_NONE)
+ res = gst_pad_query_convert (pad, *format, *stop, &fmt, stop);
+
+ if (res)
+ *format = GST_FORMAT_TIME;
+
+ return res;
+}
+
+/* perform seek in push based mode:
+ find BYTE position to move to based on time and delegate to upstream
+*/
+static gboolean
+gst_qtdemux_do_push_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
+{
+ gdouble rate;
+ GstFormat format;
+ GstSeekFlags flags;
+ GstSeekType cur_type, stop_type;
+ gint64 cur, stop;
+ gboolean res;
+ gint64 byte_cur;
+
+ GST_DEBUG_OBJECT (qtdemux, "doing push-based seek");
+
+ gst_event_parse_seek (event, &rate, &format, &flags,
+ &cur_type, &cur, &stop_type, &stop);
+
+ if (stop_type != GST_SEEK_TYPE_NONE)
+ goto unsupported_seek;
+ stop = -1;
+
+ /* convert to TIME if needed and possible */
+ if (!gst_qtdemux_convert_seek (pad, &format, cur_type, &cur,
+ stop_type, &stop))
+ goto no_format;
+
+ /* find reasonable corresponding BYTE position,
+ * also try to mind about keyframes, since we can not go back a bit for them
+ * later on */
+ gst_qtdemux_adjust_seek (qtdemux, cur, NULL, &byte_cur);
+
+ if (byte_cur == -1)
+ goto abort_seek;
+
+ GST_DEBUG_OBJECT (qtdemux, "Pushing BYTE seek rate %g, "
+ "start %" G_GINT64_FORMAT ", stop %" G_GINT64_FORMAT, rate, byte_cur,
+ stop);
+ /* BYTE seek event */
+ event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, cur_type, byte_cur,
+ stop_type, stop);
+ res = gst_pad_push_event (qtdemux->sinkpad, event);
+
+ return res;
+
+ /* ERRORS */
+abort_seek:
+ {
+ GST_DEBUG_OBJECT (qtdemux, "could not determine byte position to seek to, "
+ "seek aborted.");
+ return FALSE;
+ }
+unsupported_seek:
+ {
+ GST_DEBUG_OBJECT (qtdemux, "unsupported seek, seek aborted.");
+ return FALSE;
+ }
+no_format:
+ {
+ GST_DEBUG_OBJECT (qtdemux, "unsupported format given, seek aborted.");
+ return FALSE;
+ }
+}
+
/* perform the seek.
*
* We set all segment_indexes in the streams to unknown and
@@ -716,71 +902,11 @@ gst_qtdemux_perform_seek (GstQTDemux * qtdemux, GstSegment * segment)
GST_TIME_ARGS (desired_offset));
if (segment->flags & GST_SEEK_FLAG_KEY_UNIT) {
- guint64 min_offset;
-
- min_offset = desired_offset;
-
- /* for each stream, find the index of the sample in the segment
- * and move back to the previous keyframe. */
- for (n = 0; n < qtdemux->n_streams; n++) {
- QtDemuxStream *str;
- guint32 index, kindex;
- guint32 seg_idx;
- guint64 media_start;
- guint64 media_time;
- guint64 seg_time;
- QtDemuxSegment *seg;
-
- 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;
+ gint64 min_offset;
- /* 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);
-
- /* if the keyframe is at a different position, we need to update the
- * requiested seek time */
- if (index != kindex) {
- index = kindex;
-
- /* get timestamp of keyframe */
- media_time = str->samples[kindex].timestamp;
- GST_DEBUG_OBJECT (qtdemux, "keyframe at %u with time %" GST_TIME_FORMAT,
- kindex, GST_TIME_ARGS (media_time));
-
- /* keyframes in the segment get a chance to change the
- * desired_offset. keyframes out of the segment are
- * ignored. */
- if (media_time >= seg->media_start) {
- guint64 seg_time;
-
- /* this keyframe is inside the segment, convert back to
- * segment time */
- seg_time = (media_time - seg->media_start) + seg->time;
- if (seg_time < min_offset)
- min_offset = seg_time;
- }
- }
- }
+ gst_qtdemux_adjust_seek (qtdemux, desired_offset, &min_offset, NULL);
GST_DEBUG_OBJECT (qtdemux, "keyframe seek, align to %"
- GST_TIME_FORMAT, GST_TIME_ARGS (desired_offset));
+ GST_TIME_FORMAT, GST_TIME_ARGS (min_offset));
desired_offset = min_offset;
}
@@ -826,20 +952,9 @@ gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
/* we have to have a format as the segment format. Try to convert
* if not. */
- if (format != GST_FORMAT_TIME) {
- GstFormat fmt;
-
- fmt = GST_FORMAT_TIME;
- res = TRUE;
- if (cur_type != GST_SEEK_TYPE_NONE)
- res = gst_pad_query_convert (pad, format, cur, &fmt, &cur);
- if (res && stop_type != GST_SEEK_TYPE_NONE)
- res = gst_pad_query_convert (pad, format, stop, &fmt, &stop);
- if (!res)
- goto no_format;
-
- format = fmt;
- }
+ if (!gst_qtdemux_convert_seek (pad, &format, cur_type, &cur,
+ stop_type, &stop))
+ goto no_format;
} else {
GST_DEBUG_OBJECT (qtdemux, "doing seek without event");
flags = 0;
@@ -947,8 +1062,11 @@ gst_qtdemux_handle_src_event (GstPad * pad, GstEvent * event)
case GST_EVENT_SEEK:
if (qtdemux->pullbased) {
res = gst_qtdemux_do_seek (qtdemux, pad, event);
+ } else if (qtdemux->state == QTDEMUX_STATE_MOVIE && qtdemux->n_streams) {
+ res = gst_qtdemux_do_push_seek (qtdemux, pad, event);
} else {
- GST_DEBUG_OBJECT (qtdemux, "cannot seek in streaming mode");
+ GST_DEBUG_OBJECT (qtdemux,
+ "ignoring seek in push mode in current state");
res = FALSE;
}
gst_event_unref (event);
@@ -968,18 +1086,151 @@ gst_qtdemux_handle_src_event (GstPad * pad, GstEvent * event)
return res;
}
+/* stream/index return sample that is min/max w.r.t. byte position,
+ * time is min/max w.r.t. time of samples,
+ * the latter need not be time of the former sample */
+static void
+gst_qtdemux_find_sample (GstQTDemux * qtdemux, gint64 byte_pos, gboolean fw,
+ gboolean set, QtDemuxStream ** _stream, gint * _index, gint64 * _time)
+{
+ gint i, n, index;
+ gint64 time, min_time;
+ QtDemuxStream *stream;
+
+ min_time = -1;
+ stream = NULL;
+ index = -1;
+
+ for (n = 0; n < qtdemux->n_streams; ++n) {
+ QtDemuxStream *str;
+ gint inc;
+
+ str = qtdemux->streams[n];
+
+ if (fw) {
+ i = 0;
+ inc = 1;
+ } else {
+ i = str->n_samples - 1;
+ inc = -1;
+ }
+ for (; (i >= 0) && (i < str->n_samples); i += inc) {
+ if (str->samples[i].size &&
+ ((fw && (str->samples[i].offset >= byte_pos)) ||
+ (!fw &&
+ (str->samples[i].offset + str->samples[i].size <=
+ byte_pos)))) {
+ /* move stream to first available sample */
+ if (set)
+ gst_qtdemux_move_stream (qtdemux, str, i);
+ /* determine min/max time */
+ time = str->samples[i].timestamp + str->samples[i].pts_offset;
+ if (min_time == -1 || (fw && min_time > time) ||
+ (!fw && min_time < time)) {
+ min_time = time;
+ }
+ /* determine stream with leading sample, to get its position */
+ /* only needed in fw case */
+ if (fw && (!stream ||
+ str->samples[i].offset < stream->samples[index].offset)) {
+ stream = str;
+ index = i;
+ }
+ break;
+ }
+ }
+ }
+
+ if (_time)
+ *_time = min_time;
+ if (_stream)
+ *_stream = stream;
+ if (_index)
+ *_index = index;
+}
+
static gboolean
gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstEvent * event)
{
GstQTDemux *demux = GST_QTDEMUX (GST_PAD_PARENT (sinkpad));
gboolean res;
+ GST_LOG_OBJECT (demux, "handling %s event", GST_EVENT_TYPE_NAME (event));
+
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_NEWSEGMENT:
- /* We need to convert it to a GST_FORMAT_TIME new segment */
+ {
+ GstFormat format;
+ gdouble rate, arate;
+ gint64 start, stop, time, offset = 0;
+ QtDemuxStream *stream;
+ gint idx;
+ gboolean update;
+ GstSegment segment;
+
+ /* some debug output */
+ gst_segment_init (&segment, GST_FORMAT_UNDEFINED);
+ gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format,
+ &start, &stop, &time);
+ gst_segment_set_newsegment_full (&segment, update, rate, arate, format,
+ start, stop, time);
+ GST_DEBUG_OBJECT (demux,
+ "received format %d newsegment %" GST_SEGMENT_FORMAT, format,
+ &segment);
+
+ /* chain will send initial newsegment after pads have been added */
+ if (demux->state != QTDEMUX_STATE_MOVIE || !demux->n_streams) {
+ GST_DEBUG_OBJECT (demux, "still starting, eating event");
+ goto exit;
+ }
+
+ /* we only expect a BYTE segment, e.g. following a seek */
+ if (format == GST_FORMAT_BYTES) {
+ if (start > 0) {
+ offset = start;
+ gst_qtdemux_find_sample (demux, start, TRUE, FALSE, NULL, NULL,
+ &start);
+ start = MAX (start, 0);
+ }
+ if (stop > 0) {
+ gst_qtdemux_find_sample (demux, stop, FALSE, FALSE, NULL, NULL,
+ &stop);
+ stop = MAX (stop, 0);
+ }
+ } else {
+ GST_DEBUG_OBJECT (demux, "unsupported segment format, ignoring");
+ goto exit;
+ }
+
+ /* accept upstream's notion of segment and distribute along */
+ gst_segment_set_newsegment_full (&demux->segment, update, rate, arate,
+ GST_FORMAT_TIME, start, stop, start);
+ GST_DEBUG_OBJECT (demux, "Pushing newseg update %d, rate %g, "
+ "applied rate %g, format %d, start %" G_GINT64_FORMAT ", "
+ "stop %" G_GINT64_FORMAT, update, rate, arate, GST_FORMAT_TIME,
+ start, stop);
+ gst_qtdemux_push_event (demux,
+ gst_event_new_new_segment_full (update, rate, arate, GST_FORMAT_TIME,
+ start, stop, start));
+
+ /* clear leftover in current segment, if any */
+ gst_adapter_clear (demux->adapter);
+ /* set up streaming thread */
+ gst_qtdemux_find_sample (demux, offset, TRUE, TRUE, &stream, &idx, NULL);
+ demux->offset = offset;
+ if (stream) {
+ demux->todrop = stream->samples[idx].offset - offset;
+ demux->neededbytes = demux->todrop + stream->samples[idx].size;
+ } else {
+ /* set up for EOS */
+ demux->neededbytes = -1;
+ demux->todrop = 0;
+ }
+ exit:
gst_event_unref (event);
res = TRUE;
break;
+ }
case GST_EVENT_EOS:
/* If we are in push mode, and get an EOS before we've seen any streams,
* then error out - we have nowhere to send the EOS */
@@ -2272,7 +2523,8 @@ gst_qtdemux_chain (GstPad * sinkpad, GstBuffer * inbuf)
goto unknown_stream;
/* first buffer? */
- /* FIXME : this should be handled in sink_event */
+ /* initial newsegment sent here after having added pads,
+ * possible others in sink_event */
if (demux->last_ts == GST_CLOCK_TIME_NONE) {
gst_qtdemux_push_event (demux,
gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME,