diff options
author | Edward Hervey <bilboed@bilboed.com> | 2015-01-22 17:33:12 +0100 |
---|---|---|
committer | Edward Hervey <bilboed@bilboed.com> | 2015-02-18 12:20:33 +0100 |
commit | 738aad2177bcd1ce52f18b9f0776e98dd94680bb (patch) | |
tree | e4330af1a6f8a9b6f6fd76c1de182de7b525acf3 | |
parent | 1b47552a4ff1605a2afe23a076702cca13c83d8c (diff) |
qtdemux: WIP : qtdemux push-mode fragmented seeking
1) Try to look for MFRO/MFRA and fill it up
FIXME : Streamline more with existing (pull-based) code
2) Find what is the optimal fragment for a given seek
Remember the fragment start time and offset and seek to that
3) When we get the corresponding SEGMENT, put ourself in the INITIAL state
set the offset to the fragment offset and clean up our streams.
We need to do that in the event handler to guarantee the chain function
isn't currently handling data
-rw-r--r-- | gst/isomp4/qtdemux.c | 569 | ||||
-rw-r--r-- | gst/isomp4/qtdemux.h | 4 |
2 files changed, 439 insertions, 134 deletions
diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c index 10fe9ba08..47cafe2b5 100644 --- a/gst/isomp4/qtdemux.c +++ b/gst/isomp4/qtdemux.c @@ -390,7 +390,9 @@ enum QtDemuxState QTDEMUX_STATE_INITIAL, /* Initial state (haven't got the header yet) */ QTDEMUX_STATE_HEADER, /* Parsing the header */ QTDEMUX_STATE_MOVIE, /* Parsing/Playing the media data */ - QTDEMUX_STATE_BUFFER_MDAT /* Buffering the mdat atom */ + QTDEMUX_STATE_BUFFER_MDAT, /* Buffering the mdat atom (push-based) */ + QTDEMUX_STATE_MFRO_SEARCH, /* Searching for mfro (push-based) */ + QTDEMUX_STATE_MFRA_SEARCH, /* Searching for mfra (push-based) */ }; static GNode *qtdemux_tree_get_child_by_type (GNode * node, guint32 fourcc); @@ -496,6 +498,7 @@ static GstFlowReturn qtdemux_prepare_streams (GstQTDemux * qtdemux); static void qtdemux_do_allocation (GstQTDemux * qtdemux, QtDemuxStream * stream); +static void qtdemux_parse_mfra (GstQTDemux * qtdemux, GNode * mfra_node); static gboolean qtdemux_pull_mfro_mfra (GstQTDemux * qtdemux); static void check_update_duration (GstQTDemux * qtdemux, GstClockTime duration); @@ -742,13 +745,18 @@ done: static gboolean gst_qtdemux_get_duration (GstQTDemux * qtdemux, GstClockTime * duration) { - gboolean res = TRUE; + gboolean res = FALSE; *duration = GST_CLOCK_TIME_NONE; + GST_DEBUG_OBJECT (qtdemux, + "Querying duration : %" G_GUINT64_FORMAT " %" G_GUINT32_FORMAT, + qtdemux->duration, qtdemux->timescale); + if (qtdemux->duration != 0) { if (qtdemux->duration != G_MAXINT64 && qtdemux->timescale != 0) { *duration = QTTIME_TO_GSTTIME (qtdemux, qtdemux->duration); + res = TRUE; } } return res; @@ -1178,6 +1186,248 @@ gst_qtdemux_move_stream (GstQTDemux * qtdemux, QtDemuxStream * str, str->discont = TRUE; } +static const QtDemuxRandomAccessEntry * +gst_qtdemux_stream_seek_fragment (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstClockTime pos, gboolean after) +{ + QtDemuxRandomAccessEntry *entries = stream->ra_entries; + guint n_entries = stream->n_ra_entries; + guint i; + + GST_DEBUG_OBJECT (qtdemux, "stream %p pos:%" GST_TIME_FORMAT " after:%d", + stream, GST_TIME_ARGS (pos), after); + + /* we assume the table is sorted */ + for (i = 0; i < n_entries; ++i) { + GST_DEBUG_OBJECT (qtdemux, "Entry %d ts:%" GST_TIME_FORMAT, + i, GST_TIME_ARGS (entries[i].ts)); + if (entries[i].ts > pos) + break; + } + + /* FIXME: maybe save first moof_offset somewhere instead, but for now it's + * probably okay to assume that the index lists the very first fragment */ + if (i == 0) + return &entries[0]; + + if (after) + return &entries[i]; + else + return &entries[i - 1]; +} + +/* Find the optimal position in a fragmented file for a given + * position. + * + * will set moof_offset and return the corrected time position + * + * PUSH-BASED + */ + +static GstClockTime +gst_qtdemux_find_fragment_position_push (GstQTDemux * qtdemux, + GstClockTime position) +{ + const QtDemuxRandomAccessEntry *best_entry = NULL; + guint i; + + GST_DEBUG_OBJECT (qtdemux, "position : %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + + GST_OBJECT_LOCK (qtdemux); + + g_assert (qtdemux->n_streams > 0); + + for (i = 0; i < qtdemux->n_streams; i++) { + const QtDemuxRandomAccessEntry *entry; + QtDemuxStream *stream; + gboolean is_audio_or_video; + + stream = qtdemux->streams[i]; + + if (stream->ra_entries == NULL) + continue; + + if (stream->subtype == FOURCC_vide || stream->subtype == FOURCC_soun) + is_audio_or_video = TRUE; + else + is_audio_or_video = FALSE; + + entry = + gst_qtdemux_stream_seek_fragment (qtdemux, stream, + position, !is_audio_or_video); + + GST_INFO_OBJECT (stream->pad, "%" GST_TIME_FORMAT " at offset " + "%" G_GUINT64_FORMAT, GST_TIME_ARGS (entry->ts), entry->moof_offset); + + /* decide position to jump to just based on audio/video tracks, not subs */ + if (!is_audio_or_video) + continue; + + if (best_entry == NULL || entry->moof_offset < best_entry->moof_offset) + best_entry = entry; + } + + if (best_entry == NULL) + return GST_CLOCK_TIME_NONE; + + /* Set best entry on all streams */ + for (i = 0; i < qtdemux->n_streams; i++) { + QtDemuxStream *stream; + stream = qtdemux->streams[i]; + stream->pending_seek = best_entry; + } + + GST_INFO_OBJECT (qtdemux, "seek to %" GST_TIME_FORMAT ", best fragment " + "moof offset: %" G_GUINT64_FORMAT ", ts %" GST_TIME_FORMAT, + GST_TIME_ARGS (qtdemux->streams[0]->time_position), + best_entry->moof_offset, GST_TIME_ARGS (best_entry->ts)); + + qtdemux->moof_offset = best_entry->moof_offset; + + GST_OBJECT_UNLOCK (qtdemux); + return best_entry->ts; +} + +/* Called when the new BYTE segment has been received */ +static gboolean +gst_qtdemux_do_fragmented_seek_push (GstQTDemux * qtdemux, + GstClockTime position) +{ + const QtDemuxRandomAccessEntry *best_entry = NULL; + guint i; + + GST_OBJECT_LOCK (qtdemux); + + g_assert (qtdemux->n_streams > 0); + + for (i = 0; i < qtdemux->n_streams; i++) { + const QtDemuxRandomAccessEntry *entry; + QtDemuxStream *stream; + gboolean is_audio_or_video; + + stream = qtdemux->streams[i]; + + g_free (stream->samples); + stream->samples = NULL; + stream->n_samples = 0; + stream->stbl_index = -1; /* no samples have yet been parsed */ + stream->sample_index = -1; + + if (stream->ra_entries == NULL) + continue; + + if (stream->subtype == FOURCC_vide || stream->subtype == FOURCC_soun) + is_audio_or_video = TRUE; + else + is_audio_or_video = FALSE; + + entry = + gst_qtdemux_stream_seek_fragment (qtdemux, stream, + position, !is_audio_or_video); + + GST_INFO_OBJECT (stream->pad, "%" GST_TIME_FORMAT " at offset " + "%" G_GUINT64_FORMAT, GST_TIME_ARGS (entry->ts), entry->moof_offset); + + stream->pending_seek = entry; + + /* decide position to jump to just based on audio/video tracks, not subs */ + if (!is_audio_or_video) + continue; + + if (best_entry == NULL || entry->moof_offset < best_entry->moof_offset) + best_entry = entry; + } + + if (best_entry == NULL) + return FALSE; + + GST_INFO_OBJECT (qtdemux, "seek to %" GST_TIME_FORMAT ", best fragment " + "moof offset: %" G_GUINT64_FORMAT ", ts %" GST_TIME_FORMAT, + GST_TIME_ARGS (qtdemux->streams[0]->time_position), + best_entry->moof_offset, GST_TIME_ARGS (best_entry->ts)); + + /* Set best entry on all streams */ + for (i = 0; i < qtdemux->n_streams; i++) { + QtDemuxStream *stream; + stream = qtdemux->streams[i]; + stream->pending_seek = best_entry; + } + + qtdemux->moof_offset = best_entry->moof_offset; + + GST_OBJECT_UNLOCK (qtdemux); + return TRUE; +} + +/* Called *after* time_position has been figured out and flushing + * has been done */ +static gboolean +gst_qtdemux_do_fragmented_seek_pull (GstQTDemux * qtdemux) +{ + const QtDemuxRandomAccessEntry *best_entry = NULL; + guint i; + + GST_OBJECT_LOCK (qtdemux); + + g_assert (qtdemux->n_streams > 0); + + for (i = 0; i < qtdemux->n_streams; i++) { + const QtDemuxRandomAccessEntry *entry; + QtDemuxStream *stream; + gboolean is_audio_or_video; + + stream = qtdemux->streams[i]; + + g_free (stream->samples); + stream->samples = NULL; + stream->n_samples = 0; + stream->stbl_index = -1; /* no samples have yet been parsed */ + stream->sample_index = -1; + + if (stream->ra_entries == NULL) + continue; + + if (stream->subtype == FOURCC_vide || stream->subtype == FOURCC_soun) + is_audio_or_video = TRUE; + else + is_audio_or_video = FALSE; + + entry = + gst_qtdemux_stream_seek_fragment (qtdemux, stream, + stream->time_position, !is_audio_or_video); + + GST_INFO_OBJECT (stream->pad, "%" GST_TIME_FORMAT " at offset " + "%" G_GUINT64_FORMAT, GST_TIME_ARGS (entry->ts), entry->moof_offset); + + stream->pending_seek = entry; + + /* decide position to jump to just based on audio/video tracks, not subs */ + if (!is_audio_or_video) + continue; + + if (best_entry == NULL || entry->moof_offset < best_entry->moof_offset) + best_entry = entry; + } + + if (best_entry == NULL) + return FALSE; + + GST_INFO_OBJECT (qtdemux, "seek to %" GST_TIME_FORMAT ", best fragment " + "moof offset: %" G_GUINT64_FORMAT ", ts %" GST_TIME_FORMAT, + GST_TIME_ARGS (qtdemux->streams[0]->time_position), + best_entry->moof_offset, GST_TIME_ARGS (best_entry->ts)); + + qtdemux->moof_offset = best_entry->moof_offset; + + qtdemux_add_fragmented_samples (qtdemux); + + GST_OBJECT_UNLOCK (qtdemux); + return TRUE; +} + + + static void gst_qtdemux_adjust_seek (GstQTDemux * qtdemux, gint64 desired_time, gboolean use_sparse, gint64 * key_time, gint64 * key_offset) @@ -1321,11 +1571,23 @@ gst_qtdemux_do_push_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event) original_stop = stop; stop = -1; - /* 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, FALSE, &key_cur, &byte_cur); - + /* If working in fragmented, search in random access entries */ + if (qtdemux->fragmented) { + cur = gst_qtdemux_find_fragment_position_push (qtdemux, cur); + if (!GST_CLOCK_TIME_IS_VALID (cur)) + goto fragment_fail; + /* seek to the moof_offset */ + + byte_cur = qtdemux->moof_offset; + /* qtdemux->state = QTDEMUX_STATE_INITIAL; */ + qtdemux->doing_fragment_seek = TRUE; + } else { + + /* 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, FALSE, &key_cur, &byte_cur); + } if (byte_cur == -1) goto abort_seek; @@ -1353,6 +1615,9 @@ gst_qtdemux_do_push_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event) stop_type, stop); gst_event_set_seqnum (event, seqnum); res = gst_pad_push_event (qtdemux->sinkpad, event); + if (qtdemux->fragmented) { + qtdemux->neededbytes = 16; + } return res; @@ -1373,6 +1638,12 @@ no_format: GST_DEBUG_OBJECT (qtdemux, "unsupported format given, seek aborted."); return FALSE; } +fragment_fail: + { + GST_WARNING_OBJECT (qtdemux, + "Could not find fragment position, seek aborted"); + return FALSE; + } } /* perform the seek. @@ -1611,7 +1882,8 @@ gst_qtdemux_handle_src_event (GstPad * pad, GstObject * parent, res = gst_qtdemux_do_push_seek (qtdemux, pad, event); } else { GST_DEBUG_OBJECT (qtdemux, - "ignoring seek in push mode in current state"); + "ignoring seek in push mode in current state (fragmented:%d)", + qtdemux->fragmented); res = FALSE; } gst_event_unref (event); @@ -1961,7 +2233,8 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent, "not in time format"); /* chain will send initial newsegment after pads have been added */ - if (demux->state != QTDEMUX_STATE_MOVIE || !demux->n_streams) { + if (!demux->doing_fragment_seek && (demux->state != QTDEMUX_STATE_MOVIE + || !demux->n_streams)) { GST_DEBUG_OBJECT (demux, "still starting, eating event"); goto exit; } @@ -1987,6 +2260,12 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent, "segment %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, GST_TIME_ARGS (segment.start), GST_TIME_ARGS (segment.stop)); GST_OBJECT_UNLOCK (demux); + if (demux->doing_fragment_seek) { + /* Finally reset the streams before the new data comes in */ + gst_qtdemux_do_fragmented_seek_push (demux, segment.start); + demux->doing_fragment_seek = FALSE; + demux->state = QTDEMUX_STATE_INITIAL; + } } /* we only expect a BYTE segment, e.g. following a seek */ @@ -2057,7 +2336,7 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent, demux->neededbytes = demux->todrop + stream->samples[idx].size; } else { /* set up for EOS */ - if (demux->upstream_newsegment) { + if (demux->upstream_newsegment || demux->state != QTDEMUX_STATE_MOVIE) { demux->neededbytes = 16; } else { demux->neededbytes = -1; @@ -2271,6 +2550,18 @@ gst_qtdemux_change_state (GstElement * element, GstStateChange transition) return result; } +static gboolean +qtdemux_parse_mfro (GstQTDemux * qtdemux, const guint8 * buffer, gint length, + guint32 * mfra_size) +{ + if (length >= 16) { + *mfra_size = GST_READ_UINT32_BE (buffer + 12); + return TRUE; + } + return FALSE; +} + + static void qtdemux_parse_ftyp (GstQTDemux * qtdemux, const guint8 * buffer, gint length) { @@ -2681,8 +2972,9 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun, goto index_too_big; GST_DEBUG_OBJECT (qtdemux, "allocating n_samples %u * %u (%.2f MB)", - stream->n_samples, (guint) sizeof (QtDemuxSample), - stream->n_samples * sizeof (QtDemuxSample) / (1024.0 * 1024.0)); + stream->n_samples + samples_count, (guint) sizeof (QtDemuxSample), + (stream->n_samples + + samples_count) * sizeof (QtDemuxSample) / (1024.0 * 1024.0)); /* create a new array of samples if it's the first sample parsed */ if (stream->n_samples == 0) @@ -3035,6 +3327,7 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length, traf_node = qtdemux_tree_get_sibling_by_type (traf_node, FOURCC_traf); } g_node_destroy (moof_node); + return TRUE; missing_tfhd: @@ -3145,11 +3438,13 @@ qtdemux_parse_tfra (GstQTDemux * qtdemux, GNode * tfra_node) #endif } + GST_DEBUG_OBJECT (qtdemux, "Final time seen is %" GST_TIME_FORMAT, + GST_TIME_ARGS (time)); check_update_duration (qtdemux, time); return TRUE; -/* ERRORS */ + /* ERRORS */ unknown_trackid: { GST_WARNING_OBJECT (qtdemux, "Couldn't find stream for track %u", track_id); @@ -3167,6 +3462,19 @@ no_samples: } } +static void +qtdemux_parse_mfra (GstQTDemux * qtdemux, GNode * mfra_node) +{ + GNode *tfra_node; + tfra_node = qtdemux_tree_get_child_by_type (mfra_node, FOURCC_tfra); + + while (tfra_node) { + qtdemux_parse_tfra (qtdemux, tfra_node); + /* iterate all siblings */ + tfra_node = qtdemux_tree_get_sibling_by_type (tfra_node, FOURCC_tfra); + } +} + static gboolean qtdemux_pull_mfro_mfra (GstQTDemux * qtdemux) { @@ -3175,7 +3483,7 @@ qtdemux_pull_mfro_mfra (GstQTDemux * qtdemux) GstBuffer *mfro = NULL, *mfra = NULL; GstFlowReturn flow; gboolean ret = FALSE; - GNode *mfra_node, *tfra_node; + GNode *mfra_node; guint64 mfra_offset = 0; guint32 fourcc, mfra_size; gint64 len; @@ -3217,14 +3525,8 @@ qtdemux_pull_mfro_mfra (GstQTDemux * qtdemux) mfra_node = g_node_new ((guint8 *) mfra_map.data); qtdemux_parse_node (qtdemux, mfra_node, mfra_map.data, mfra_map.size); + qtdemux_parse_mfra (qtdemux, mfra_node); - tfra_node = qtdemux_tree_get_child_by_type (mfra_node, FOURCC_tfra); - - while (tfra_node) { - qtdemux_parse_tfra (qtdemux, tfra_node); - /* iterate all siblings */ - tfra_node = qtdemux_tree_get_sibling_by_type (tfra_node, FOURCC_tfra); - } g_node_destroy (mfra_node); GST_INFO_OBJECT (qtdemux, "parsed movie fragment random access box (mfra)"); @@ -3244,7 +3546,7 @@ exit: } return ret; -/* ERRORS */ + /* ERRORS */ size_query_failed: { GST_WARNING_OBJECT (qtdemux, "could not query upstream size"); @@ -3813,8 +4115,8 @@ gst_qtdemux_activate_segment (GstQTDemux * qtdemux, QtDemuxStream * stream, /* find keyframe of the target index */ kf_index = gst_qtdemux_find_keyframe (qtdemux, stream, index); -/* *INDENT-OFF* */ -/* indent does stupid stuff with stream->samples[].timestamp */ + /* *INDENT-OFF* */ + /* indent does stupid stuff with stream->samples[].timestamp */ /* if we move forwards, we don't have to go back to the previous * keyframe since we already sent that. We can also just jump to @@ -3841,7 +4143,7 @@ gst_qtdemux_activate_segment (GstQTDemux * qtdemux, QtDemuxStream * stream, gst_qtdemux_move_stream (qtdemux, stream, kf_index); } -/* *INDENT-ON* */ + /* *INDENT-ON* */ return TRUE; } @@ -3907,18 +4209,20 @@ gst_qtdemux_prepare_current_sample (GstQTDemux * qtdemux, if (!qtdemux->fragmented) goto eos; - GST_INFO_OBJECT (qtdemux, "out of samples, trying to add more"); - do { - GstFlowReturn flow; + if (qtdemux->pullbased) { + GST_INFO_OBJECT (qtdemux, "out of samples, trying to add more"); + do { + GstFlowReturn flow; - GST_OBJECT_LOCK (qtdemux); - flow = qtdemux_add_fragmented_samples (qtdemux); - GST_OBJECT_UNLOCK (qtdemux); + GST_OBJECT_LOCK (qtdemux); + flow = qtdemux_add_fragmented_samples (qtdemux); + GST_OBJECT_UNLOCK (qtdemux); - if (flow != GST_FLOW_OK) - goto eos; + if (flow != GST_FLOW_OK) + goto eos; + } + while (stream->sample_index >= stream->n_samples); } - while (stream->sample_index >= stream->n_samples); } if (!qtdemux_parse_samples (qtdemux, stream, stream->sample_index)) { @@ -4424,96 +4728,6 @@ exit: return ret; } -static const QtDemuxRandomAccessEntry * -gst_qtdemux_stream_seek_fragment (GstQTDemux * qtdemux, QtDemuxStream * stream, - GstClockTime pos, gboolean after) -{ - QtDemuxRandomAccessEntry *entries = stream->ra_entries; - guint n_entries = stream->n_ra_entries; - guint i; - - /* we assume the table is sorted */ - for (i = 0; i < n_entries; ++i) { - if (entries[i].ts > pos) - break; - } - - /* FIXME: maybe save first moof_offset somewhere instead, but for now it's - * probably okay to assume that the index lists the very first fragment */ - if (i == 0) - return &entries[0]; - - if (after) - return &entries[i]; - else - return &entries[i - 1]; -} - -static gboolean -gst_qtdemux_do_fragmented_seek (GstQTDemux * qtdemux) -{ - const QtDemuxRandomAccessEntry *best_entry = NULL; - guint i; - - GST_OBJECT_LOCK (qtdemux); - - g_assert (qtdemux->n_streams > 0); - - for (i = 0; i < qtdemux->n_streams; i++) { - const QtDemuxRandomAccessEntry *entry; - QtDemuxStream *stream; - gboolean is_audio_or_video; - - stream = qtdemux->streams[i]; - - g_free (stream->samples); - stream->samples = NULL; - stream->n_samples = 0; - stream->stbl_index = -1; /* no samples have yet been parsed */ - stream->sample_index = -1; - - if (stream->ra_entries == NULL) - continue; - - if (stream->subtype == FOURCC_vide || stream->subtype == FOURCC_soun) - is_audio_or_video = TRUE; - else - is_audio_or_video = FALSE; - - entry = - gst_qtdemux_stream_seek_fragment (qtdemux, stream, - stream->time_position, !is_audio_or_video); - - GST_INFO_OBJECT (stream->pad, "%" GST_TIME_FORMAT " at offset " - "%" G_GUINT64_FORMAT, GST_TIME_ARGS (entry->ts), entry->moof_offset); - - stream->pending_seek = entry; - - /* decide position to jump to just based on audio/video tracks, not subs */ - if (!is_audio_or_video) - continue; - - if (best_entry == NULL || entry->moof_offset < best_entry->moof_offset) - best_entry = entry; - } - - if (best_entry == NULL) { - GST_OBJECT_UNLOCK (qtdemux); - return FALSE; - } - - GST_INFO_OBJECT (qtdemux, "seek to %" GST_TIME_FORMAT ", best fragment " - "moof offset: %" G_GUINT64_FORMAT ", ts %" GST_TIME_FORMAT, - GST_TIME_ARGS (qtdemux->streams[0]->time_position), - best_entry->moof_offset, GST_TIME_ARGS (best_entry->ts)); - - qtdemux->moof_offset = best_entry->moof_offset; - - qtdemux_add_fragmented_samples (qtdemux); - - GST_OBJECT_UNLOCK (qtdemux); - return TRUE; -} static GstFlowReturn gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux) @@ -4537,7 +4751,7 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux) if (qtdemux->fragmented_seek_pending) { GST_INFO_OBJECT (qtdemux, "pending fragmented seek"); - gst_qtdemux_do_fragmented_seek (qtdemux); + gst_qtdemux_do_fragmented_seek_pull (qtdemux); GST_INFO_OBJECT (qtdemux, "fragmented seek done!"); qtdemux->fragmented_seek_pending = FALSE; } @@ -5155,13 +5369,14 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) case QTDEMUX_STATE_HEADER:{ const guint8 *data; guint32 fourcc; + guint64 size; GST_DEBUG_OBJECT (demux, "In header"); data = gst_adapter_map (demux->adapter, demux->neededbytes); /* parse the header */ - extract_initial_length_and_fourcc (data, demux->neededbytes, NULL, + extract_initial_length_and_fourcc (data, demux->neededbytes, &size, &fourcc); if (fourcc == FOURCC_moov) { /* in usual fragmented setup we could try to scan for more @@ -5210,7 +5425,26 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) g_node_destroy (demux->moov_node); demux->moov_node = NULL; - GST_DEBUG_OBJECT (demux, "Finished parsing the header"); + GST_DEBUG_OBJECT (demux, + "Finished parsing the header (currently at offset %" + G_GUINT64_FORMAT ")", demux->offset + size); + + /* If fragmented, look for the mfro */ + if (demux->fragmented && demux->upstream_seekable) { + demux->mdatoffset = demux->offset + size; + GST_DEBUG_OBJECT (demux, "Looking for potential mfro/mfra"); + /* Let's check the last 16 bytes for the mfro atom */ + if (qtdemux_seek_offset (demux, demux->upstream_size - 16)) { + demux->offset = demux->upstream_size - 16; + demux->neededbytes = 16; + demux->state = QTDEMUX_STATE_MFRO_SEARCH; + goto done; + } + GST_DEBUG_OBJECT (demux, "Seeking failed, resuming playback"); + demux->offset = demux->mdatoffset; + demux->neededbytes = 16; + demux->state = QTDEMUX_STATE_INITIAL; + } } } else if (fourcc == FOURCC_moof) { if ((demux->got_moov || demux->media_caps) && demux->fragmented) { @@ -5357,6 +5591,66 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) break; } + case QTDEMUX_STATE_MFRO_SEARCH: + { + const guint8 *data; + guint32 fourcc; + guint64 size; + + data = gst_adapter_map (demux->adapter, demux->neededbytes); + + /* get fourcc/length, set neededbytes */ + extract_initial_length_and_fourcc ((guint8 *) data, demux->neededbytes, + &size, &fourcc); + if (fourcc == FOURCC_mfro) { + guint32 mfra_size; + GST_DEBUG_OBJECT (demux, "Parsing [mfro]"); + /* Extract mfra location from mfro and seek to there */ + if (qtdemux_parse_mfro (demux, data, demux->neededbytes, &mfra_size) + && qtdemux_seek_offset (demux, demux->upstream_size - mfra_size)) { + demux->offset = demux->upstream_size - mfra_size; + demux->neededbytes = mfra_size; + demux->state = QTDEMUX_STATE_MFRA_SEARCH; + goto done; + } + } + gst_adapter_unmap (demux->adapter); + data = NULL; + GST_DEBUG_OBJECT (demux, + "Did not find 'mfro' at end of file, resuming normal playback"); + demux->offset = demux->mdatoffset; + demux->neededbytes = 16; + demux->state = QTDEMUX_STATE_INITIAL; + break; + } + case QTDEMUX_STATE_MFRA_SEARCH: + { + const guint8 *data; + guint32 fourcc; + guint64 size; + + data = gst_adapter_map (demux->adapter, demux->neededbytes); + + /* get fourcc/length, set neededbytes */ + extract_initial_length_and_fourcc ((guint8 *) data, demux->neededbytes, + &size, &fourcc); + if (fourcc == FOURCC_mfra) { + GNode *mfra_node; + GST_DEBUG_OBJECT (demux, "Found mfra"); + mfra_node = g_node_new ((guint8 *) data); + qtdemux_parse_node (demux, mfra_node, data, demux->neededbytes); + qtdemux_node_dump (demux, mfra_node); + qtdemux_parse_mfra (demux, mfra_node); + GST_DEBUG_OBJECT (demux, "Resume parsing"); + } + gst_adapter_unmap (demux->adapter); + data = NULL; + qtdemux_seek_offset (demux, demux->mdatoffset); + demux->offset = demux->mdatoffset; + demux->neededbytes = 16; + demux->state = QTDEMUX_STATE_INITIAL; + break; + } case QTDEMUX_STATE_MOVIE:{ GstBuffer *outbuf; QtDemuxStream *stream = NULL; @@ -5369,8 +5663,9 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) "BEGIN // in MOVIE for offset %" G_GUINT64_FORMAT, demux->offset); if (demux->fragmented) { - GST_DEBUG_OBJECT (demux, "mdat remaining %" G_GUINT64_FORMAT, - demux->mdatleft); + GST_DEBUG_OBJECT (demux, + "mdat remaining %" G_GUINT64_FORMAT " needed %u", demux->mdatleft, + demux->neededbytes); if (G_LIKELY (demux->todrop < demux->mdatleft)) { /* if needed data starts within this atom, * then it should not exceed this atom */ @@ -5464,6 +5759,10 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) duration = QTSAMPLE_DUR_DTS (stream, sample, dts); keyframe = QTSAMPLE_KEYFRAME (stream, sample); + GST_DEBUG_OBJECT (demux, + "Checking pts (%" GST_TIME_FORMAT ") against segment.stop (%" + GST_TIME_FORMAT ")", GST_TIME_ARGS (pts), + GST_TIME_ARGS (demux->segment.stop)); /* check for segment end */ if (G_UNLIKELY (demux->segment.stop != -1 && demux->segment.stop <= pts && stream->on_keyframe)) { @@ -7134,11 +7433,13 @@ done: /* if index has been completely parsed, free data that is no-longer needed */ if (n + 1 == stream->n_samples) { gst_qtdemux_stbl_free (stream); - GST_DEBUG_OBJECT (qtdemux, - "parsed all available samples; checking for more"); - while (n + 1 == stream->n_samples) - if (qtdemux_add_fragmented_samples (qtdemux) != GST_FLOW_OK) - break; + if (qtdemux->pullbased) { + GST_DEBUG_OBJECT (qtdemux, + "parsed all available samples; checking for more"); + while (n + 1 == stream->n_samples) + if (qtdemux_add_fragmented_samples (qtdemux) != GST_FLOW_OK) + break; + } } GST_OBJECT_UNLOCK (qtdemux); diff --git a/gst/isomp4/qtdemux.h b/gst/isomp4/qtdemux.h index a562e6263..c0f207cbe 100644 --- a/gst/isomp4/qtdemux.h +++ b/gst/isomp4/qtdemux.h @@ -128,6 +128,10 @@ struct _GstQTDemux { gint64 push_seek_start; gint64 push_seek_stop; + guint64 segment_base; /* The offset from which playback was started, needs to + * be subtracted from GstSegment.base to get a correct + * running time whenever a new QtSegment is activated */ + gboolean doing_fragment_seek; #if 0 /* gst index support */ GstIndex *element_index; |