mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-20 04:56:24 +00:00
qtdemux: implement seeking in fragmented mp4 files in pull mode based on the mfra table
This commit is contained in:
parent
77f37a6b22
commit
d65c3bbe7e
2 changed files with 156 additions and 5 deletions
|
@ -359,6 +359,8 @@ struct _QtDemuxStream
|
||||||
QtDemuxRandomAccessEntry *ra_entries;
|
QtDemuxRandomAccessEntry *ra_entries;
|
||||||
guint n_ra_entries;
|
guint n_ra_entries;
|
||||||
|
|
||||||
|
const QtDemuxRandomAccessEntry *pending_seek;
|
||||||
|
|
||||||
/* ctts */
|
/* ctts */
|
||||||
gboolean ctts_present;
|
gboolean ctts_present;
|
||||||
guint32 n_composition_times;
|
guint32 n_composition_times;
|
||||||
|
@ -391,6 +393,8 @@ static GNode *qtdemux_tree_get_sibling_by_type (GNode * node, guint32 fourcc);
|
||||||
static GNode *qtdemux_tree_get_sibling_by_type_full (GNode * node,
|
static GNode *qtdemux_tree_get_sibling_by_type_full (GNode * node,
|
||||||
guint32 fourcc, GstByteReader * parser);
|
guint32 fourcc, GstByteReader * parser);
|
||||||
|
|
||||||
|
static GstFlowReturn qtdemux_add_fragmented_samples (GstQTDemux * qtdemux);
|
||||||
|
|
||||||
static GstStaticPadTemplate gst_qtdemux_sink_template =
|
static GstStaticPadTemplate gst_qtdemux_sink_template =
|
||||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||||
GST_PAD_SINK,
|
GST_PAD_SINK,
|
||||||
|
@ -1425,6 +1429,9 @@ gst_qtdemux_perform_seek (GstQTDemux * qtdemux, GstSegment * segment,
|
||||||
if (segment->stop == -1)
|
if (segment->stop == -1)
|
||||||
segment->stop = segment->duration;
|
segment->stop = segment->duration;
|
||||||
|
|
||||||
|
if (qtdemux->fragmented)
|
||||||
|
qtdemux->fragmented_seek_pending = TRUE;
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2462,6 +2469,11 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun,
|
||||||
"decode ts %" G_GINT64_FORMAT, stream->track_id, d_sample_duration,
|
"decode ts %" G_GINT64_FORMAT, stream->track_id, d_sample_duration,
|
||||||
d_sample_size, d_sample_flags, *base_offset, decode_ts);
|
d_sample_size, d_sample_flags, *base_offset, decode_ts);
|
||||||
|
|
||||||
|
if (stream->pending_seek && moof_offset < stream->pending_seek->moof_offset) {
|
||||||
|
GST_INFO_OBJECT (stream->pad, "skipping trun before seek target fragment");
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
/* presence of stss or not can't really tell us much,
|
/* presence of stss or not can't really tell us much,
|
||||||
* and flags and so on tend to be marginally reliable in these files */
|
* and flags and so on tend to be marginally reliable in these files */
|
||||||
if (stream->subtype == FOURCC_soun) {
|
if (stream->subtype == FOURCC_soun) {
|
||||||
|
@ -2571,10 +2583,20 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun,
|
||||||
qtdemux->fragment_start = -1;
|
qtdemux->fragment_start = -1;
|
||||||
} else {
|
} else {
|
||||||
if (G_UNLIKELY (stream->n_samples == 0)) {
|
if (G_UNLIKELY (stream->n_samples == 0)) {
|
||||||
/* the timestamp of the first sample is also provided by the tfra entry
|
if (decode_ts > 0) {
|
||||||
* but we shouldn't rely on it as it is at the end of files */
|
|
||||||
if (decode_ts >= 0) {
|
|
||||||
timestamp = decode_ts;
|
timestamp = decode_ts;
|
||||||
|
} else if (stream->pending_seek != NULL) {
|
||||||
|
/* if we don't have a timestamp from a tfdt box, we'll use the one
|
||||||
|
* from the mfra seek table */
|
||||||
|
GST_INFO_OBJECT (stream->pad, "pending seek ts = %" GST_TIME_FORMAT,
|
||||||
|
GST_TIME_ARGS (stream->pending_seek->ts));
|
||||||
|
|
||||||
|
/* FIXME: this is not fully correct, the timestamp refers to the random
|
||||||
|
* access sample refered to in the tfra entry, which may not necessarily
|
||||||
|
* be the first sample in the tfrag/trun (but hopefully/usually is) */
|
||||||
|
timestamp =
|
||||||
|
gst_util_uint64_scale (stream->pending_seek->ts,
|
||||||
|
stream->timescale, GST_SECOND);
|
||||||
} else {
|
} else {
|
||||||
timestamp = 0;
|
timestamp = 0;
|
||||||
}
|
}
|
||||||
|
@ -2643,6 +2665,9 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun,
|
||||||
|
|
||||||
stream->n_samples += samples_count;
|
stream->n_samples += samples_count;
|
||||||
|
|
||||||
|
if (stream->pending_seek != NULL)
|
||||||
|
stream->pending_seek = NULL;
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
|
@ -3605,6 +3630,17 @@ gst_qtdemux_activate_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
|
||||||
gst_qtdemux_push_tags (qtdemux, stream);
|
gst_qtdemux_push_tags (qtdemux, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* in the fragmented case, we pick a fragment that starts before our
|
||||||
|
* desired position and rely on downstream to wait for a keyframe
|
||||||
|
* (FIXME: doesn't seem to work so well with ismv and wmv, as no parser; the
|
||||||
|
* tfra entries tells us which trun/sample the key unit is in, but we don't
|
||||||
|
* make use of this additional information at the moment) */
|
||||||
|
if (qtdemux->fragmented) {
|
||||||
|
index = 0;
|
||||||
|
stream->to_sample = G_MAXUINT32;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
/* 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 */
|
||||||
if (G_LIKELY (!QTSEGMENT_IS_EMPTY (segment))) {
|
if (G_LIKELY (!QTSEGMENT_IS_EMPTY (segment))) {
|
||||||
|
@ -3732,11 +3768,29 @@ gst_qtdemux_prepare_current_sample (GstQTDemux * qtdemux,
|
||||||
|
|
||||||
*empty = FALSE;
|
*empty = FALSE;
|
||||||
|
|
||||||
|
if (stream->sample_index == -1)
|
||||||
|
stream->sample_index = 0;
|
||||||
|
|
||||||
GST_LOG_OBJECT (qtdemux, "segment active, index = %u of %u",
|
GST_LOG_OBJECT (qtdemux, "segment active, index = %u of %u",
|
||||||
stream->sample_index, stream->n_samples);
|
stream->sample_index, stream->n_samples);
|
||||||
|
|
||||||
if (G_UNLIKELY (stream->sample_index >= stream->n_samples))
|
if (G_UNLIKELY (stream->sample_index >= stream->n_samples)) {
|
||||||
goto eos;
|
if (!qtdemux->fragmented)
|
||||||
|
goto eos;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (flow != GST_FLOW_OK)
|
||||||
|
goto eos;
|
||||||
|
}
|
||||||
|
while (stream->sample_index >= stream->n_samples);
|
||||||
|
}
|
||||||
|
|
||||||
if (!qtdemux_parse_samples (qtdemux, stream, stream->sample_index)) {
|
if (!qtdemux_parse_samples (qtdemux, stream, stream->sample_index)) {
|
||||||
GST_LOG_OBJECT (qtdemux, "Parsing of index %u failed!",
|
GST_LOG_OBJECT (qtdemux, "Parsing of index %u failed!",
|
||||||
|
@ -4244,6 +4298,95 @@ exit:
|
||||||
return ret;
|
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)
|
||||||
|
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
|
static GstFlowReturn
|
||||||
gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
|
gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
|
||||||
{
|
{
|
||||||
|
@ -4264,6 +4407,13 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
|
||||||
|
|
||||||
gst_qtdemux_push_pending_newsegment (qtdemux);
|
gst_qtdemux_push_pending_newsegment (qtdemux);
|
||||||
|
|
||||||
|
if (qtdemux->fragmented_seek_pending) {
|
||||||
|
GST_INFO_OBJECT (qtdemux, "pending fragmented seek");
|
||||||
|
gst_qtdemux_do_fragmented_seek (qtdemux);
|
||||||
|
GST_INFO_OBJECT (qtdemux, "fragmented seek done!");
|
||||||
|
qtdemux->fragmented_seek_pending = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
/* Figure out the next stream sample to output, min_time is expressed in
|
/* Figure out the next stream sample to output, min_time is expressed in
|
||||||
* global time and runs over the edit list segments. */
|
* global time and runs over the edit list segments. */
|
||||||
min_time = G_MAXUINT64;
|
min_time = G_MAXUINT64;
|
||||||
|
|
|
@ -79,6 +79,7 @@ struct _GstQTDemux {
|
||||||
guint64 duration;
|
guint64 duration;
|
||||||
|
|
||||||
gboolean fragmented;
|
gboolean fragmented;
|
||||||
|
gboolean fragmented_seek_pending;
|
||||||
guint64 moof_offset;
|
guint64 moof_offset;
|
||||||
|
|
||||||
gint state;
|
gint state;
|
||||||
|
|
Loading…
Reference in a new issue