diff options
-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; |