mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 20:21:24 +00:00
matroskademux: pull mode non-cue seeking
That is, in files that have no index (Cue), perform seek by scanning for nearest cluster with timecode before requested position. Scanning is done as a combination of interpolation and sequential scan. Fixes #617368.
This commit is contained in:
parent
4b76e95abe
commit
c626e90fda
2 changed files with 255 additions and 4 deletions
|
@ -2315,11 +2315,232 @@ gst_matroska_demux_move_to_entry (GstMatroskaDemux * demux,
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* bisect and scan through file for cluster starting before @time,
|
||||||
|
* returns fake index entry with corresponding info on cluster */
|
||||||
|
static GstMatroskaIndex *
|
||||||
|
gst_matroska_demux_search_pos (GstMatroskaDemux * demux, GstClockTime time)
|
||||||
|
{
|
||||||
|
GstMatroskaIndex *entry;
|
||||||
|
GstMatroskaDemuxState current_state;
|
||||||
|
GstClockTime otime, prev_cluster_time, current_cluster_time, cluster_time;
|
||||||
|
gint64 opos, newpos, startpos = 0, current_offset;
|
||||||
|
gint64 prev_cluster_offset, current_cluster_offset, cluster_offset;
|
||||||
|
const guint chunk = 64 * 1024;
|
||||||
|
GstBuffer *buf = NULL;
|
||||||
|
GstFlowReturn ret;
|
||||||
|
guint64 length;
|
||||||
|
guint32 id;
|
||||||
|
guint needed;
|
||||||
|
|
||||||
|
/* (under)estimate new position, resync using cluster ebml id,
|
||||||
|
* and scan forward to appropriate cluster
|
||||||
|
* (and re-estimate if need to go backward) */
|
||||||
|
|
||||||
|
prev_cluster_time = GST_CLOCK_TIME_NONE;
|
||||||
|
|
||||||
|
/* store some current state */
|
||||||
|
current_state = demux->state;
|
||||||
|
g_return_val_if_fail (current_state == GST_MATROSKA_DEMUX_STATE_DATA, NULL);
|
||||||
|
|
||||||
|
current_cluster_offset = demux->cluster_offset;
|
||||||
|
current_cluster_time = demux->cluster_time;
|
||||||
|
current_offset = demux->offset;
|
||||||
|
|
||||||
|
demux->state = GST_MATROSKA_DEMUX_STATE_SCANNING;
|
||||||
|
|
||||||
|
/* estimate using start and current position */
|
||||||
|
opos = demux->offset - demux->ebml_segment_start;
|
||||||
|
otime = demux->segment.last_stop;
|
||||||
|
|
||||||
|
retry:
|
||||||
|
GST_LOG_OBJECT (demux,
|
||||||
|
"opos: %" G_GUINT64_FORMAT ", otime: %" GST_TIME_FORMAT, opos,
|
||||||
|
GST_TIME_ARGS (otime));
|
||||||
|
newpos = gst_util_uint64_scale (opos, time, otime) - chunk;
|
||||||
|
if (newpos < 0)
|
||||||
|
newpos = 0;
|
||||||
|
/* favour undershoot */
|
||||||
|
newpos = newpos * 90 / 100;
|
||||||
|
newpos += demux->ebml_segment_start;
|
||||||
|
|
||||||
|
GST_DEBUG_OBJECT (demux,
|
||||||
|
"estimated offset for %" GST_TIME_FORMAT ": %" G_GINT64_FORMAT,
|
||||||
|
GST_TIME_ARGS (time), newpos);
|
||||||
|
|
||||||
|
/* and at least start scanning before previous scan start to avoid looping */
|
||||||
|
startpos = startpos * 90 / 100;
|
||||||
|
if (startpos && startpos < newpos)
|
||||||
|
newpos = startpos;
|
||||||
|
|
||||||
|
/* read in at newpos and scan for ebml cluster id */
|
||||||
|
startpos = newpos;
|
||||||
|
while (1) {
|
||||||
|
GstByteReader reader;
|
||||||
|
gint cluster_pos;
|
||||||
|
|
||||||
|
ret = gst_pad_pull_range (demux->sinkpad, newpos, chunk, &buf);
|
||||||
|
if (ret == GST_FLOW_UNEXPECTED) {
|
||||||
|
/* heuristic HACK */
|
||||||
|
newpos = startpos * 80 / 100;
|
||||||
|
GST_DEBUG_OBJECT (demux, "EOS; "
|
||||||
|
"new estimated offset for %" GST_TIME_FORMAT ": %" G_GINT64_FORMAT,
|
||||||
|
GST_TIME_ARGS (time), newpos);
|
||||||
|
startpos = newpos;
|
||||||
|
continue;
|
||||||
|
} else if (ret != GST_FLOW_OK) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
GST_DEBUG_OBJECT (demux, "read buffer size %d at offset %" G_GINT64_FORMAT,
|
||||||
|
GST_BUFFER_SIZE (buf), newpos);
|
||||||
|
gst_byte_reader_init_from_buffer (&reader, buf);
|
||||||
|
cluster_pos = 0;
|
||||||
|
resume:
|
||||||
|
cluster_pos = gst_byte_reader_masked_scan_uint32 (&reader, 0xffffffff,
|
||||||
|
GST_MATROSKA_ID_CLUSTER, cluster_pos,
|
||||||
|
GST_BUFFER_SIZE (buf) - cluster_pos);
|
||||||
|
if (cluster_pos >= 0) {
|
||||||
|
newpos += cluster_pos;
|
||||||
|
GST_DEBUG_OBJECT (demux,
|
||||||
|
"found cluster ebml id at offset %" G_GINT64_FORMAT, newpos);
|
||||||
|
gst_buffer_unref (buf);
|
||||||
|
buf = NULL;
|
||||||
|
/* extra checks whether we really sync'ed to a cluster:
|
||||||
|
* - either it is the first and only cluster
|
||||||
|
* - either there is a cluster after this one
|
||||||
|
* - either cluster length is undefined
|
||||||
|
*/
|
||||||
|
/* ok if first cluster (there may not a subsequent one) */
|
||||||
|
if (newpos == demux->first_cluster_offset) {
|
||||||
|
GST_DEBUG_OBJECT (demux, "cluster is first cluster -> OK");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
demux->offset = newpos;
|
||||||
|
ret =
|
||||||
|
gst_matroska_demux_peek_id_length_pull (demux, &id, &length, &needed);
|
||||||
|
if (ret != GST_FLOW_OK)
|
||||||
|
goto resume;
|
||||||
|
g_assert (id == GST_MATROSKA_ID_CLUSTER);
|
||||||
|
GST_DEBUG_OBJECT (demux, "cluster size %" G_GUINT64_FORMAT ", prefix %d",
|
||||||
|
length, needed);
|
||||||
|
/* ok if undefined length or first cluster */
|
||||||
|
if (length == G_MAXUINT64) {
|
||||||
|
GST_DEBUG_OBJECT (demux, "cluster has undefined length -> OK");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* skip cluster */
|
||||||
|
demux->offset += length + needed;
|
||||||
|
ret =
|
||||||
|
gst_matroska_demux_peek_id_length_pull (demux, &id, &length, &needed);
|
||||||
|
if (ret != GST_FLOW_OK)
|
||||||
|
goto resume;
|
||||||
|
GST_DEBUG_OBJECT (demux, "next element is %scluster",
|
||||||
|
id == GST_MATROSKA_ID_CLUSTER ? "" : "not ");
|
||||||
|
if (id == GST_MATROSKA_ID_CLUSTER)
|
||||||
|
break;
|
||||||
|
/* not ok, resume */
|
||||||
|
goto resume;
|
||||||
|
} else {
|
||||||
|
/* partial cluster id may have been in tail of buffer */
|
||||||
|
newpos += MAX (GST_BUFFER_SIZE (buf), 4) - 3;
|
||||||
|
gst_buffer_unref (buf);
|
||||||
|
buf = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* then start scanning and parsing for cluster time,
|
||||||
|
* re-estimate if overshoot, otherwise next cluster and so on */
|
||||||
|
demux->offset = newpos;
|
||||||
|
demux->cluster_time = cluster_time = GST_CLOCK_TIME_NONE;
|
||||||
|
while (1) {
|
||||||
|
guint64 cluster_size;
|
||||||
|
|
||||||
|
/* peek and parse some elements */
|
||||||
|
ret = gst_matroska_demux_peek_id_length_pull (demux, &id, &length, &needed);
|
||||||
|
if (ret != GST_FLOW_OK)
|
||||||
|
goto error;
|
||||||
|
GST_LOG_OBJECT (demux, "Offset %" G_GUINT64_FORMAT ", Element id 0x%x, "
|
||||||
|
"size %" G_GUINT64_FORMAT ", needed %d", demux->offset, id,
|
||||||
|
length, needed);
|
||||||
|
ret = gst_matroska_demux_parse_id (demux, id, length, needed);
|
||||||
|
if (ret != GST_FLOW_OK)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
if (id == GST_MATROSKA_ID_CLUSTER) {
|
||||||
|
cluster_time = GST_CLOCK_TIME_NONE;
|
||||||
|
if (length == G_MAXUINT64)
|
||||||
|
cluster_size = 0;
|
||||||
|
else
|
||||||
|
cluster_size = length + needed;
|
||||||
|
}
|
||||||
|
if (demux->cluster_time != GST_CLOCK_TIME_NONE &&
|
||||||
|
cluster_time == GST_CLOCK_TIME_NONE) {
|
||||||
|
cluster_time = demux->cluster_time * demux->time_scale;
|
||||||
|
cluster_offset = demux->cluster_offset;
|
||||||
|
GST_DEBUG_OBJECT (demux, "found cluster at offset %" G_GINT64_FORMAT
|
||||||
|
" with time %" GST_TIME_FORMAT, cluster_offset,
|
||||||
|
GST_TIME_ARGS (cluster_time));
|
||||||
|
if (cluster_time > time) {
|
||||||
|
GST_DEBUG_OBJECT (demux, "overshot target");
|
||||||
|
/* cluster overshoots */
|
||||||
|
if (prev_cluster_time != GST_CLOCK_TIME_NONE) {
|
||||||
|
/* prev cluster did not overshoot, so prev cluster is target */
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
/* re-estimate using this new position info */
|
||||||
|
opos = cluster_offset;
|
||||||
|
otime = cluster_time;
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* cluster undershoots, goto next one */
|
||||||
|
prev_cluster_time = cluster_time;
|
||||||
|
prev_cluster_offset = cluster_offset;
|
||||||
|
/* skip cluster if length is defined,
|
||||||
|
* otherwise will be skippingly parsed into */
|
||||||
|
if (cluster_size) {
|
||||||
|
GST_DEBUG_OBJECT (demux, "skipping to next cluster");
|
||||||
|
demux->offset = cluster_offset + cluster_size;
|
||||||
|
demux->cluster_time = GST_CLOCK_TIME_NONE;
|
||||||
|
} else {
|
||||||
|
GST_DEBUG_OBJECT (demux, "parsing/skipping cluster elements");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (ret == GST_FLOW_UNEXPECTED) {
|
||||||
|
if (prev_cluster_time != GST_CLOCK_TIME_NONE)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = g_new0 (GstMatroskaIndex, 1);
|
||||||
|
entry->time = prev_cluster_time;
|
||||||
|
entry->pos = prev_cluster_offset - demux->ebml_segment_start;
|
||||||
|
GST_DEBUG_OBJECT (demux, "simulated index entry; time %" GST_TIME_FORMAT
|
||||||
|
", pos %" G_GUINT64_FORMAT, GST_TIME_ARGS (entry->time), entry->pos);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
if (buf)
|
||||||
|
gst_buffer_unref (buf);
|
||||||
|
|
||||||
|
/* restore some state */
|
||||||
|
demux->cluster_offset = current_cluster_offset;
|
||||||
|
demux->cluster_time = current_cluster_time;
|
||||||
|
demux->offset = current_offset;
|
||||||
|
demux->state = current_state;
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux,
|
gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux,
|
||||||
GstPad * pad, GstEvent * event)
|
GstPad * pad, GstEvent * event)
|
||||||
{
|
{
|
||||||
GstMatroskaIndex *entry = NULL;
|
GstMatroskaIndex *entry = NULL;
|
||||||
|
GstMatroskaIndex scan_entry;
|
||||||
GstSeekFlags flags;
|
GstSeekFlags flags;
|
||||||
GstSeekType cur_type, stop_type;
|
GstSeekType cur_type, stop_type;
|
||||||
GstFormat format;
|
GstFormat format;
|
||||||
|
@ -2361,10 +2582,13 @@ gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux,
|
||||||
if ((entry = gst_matroskademux_do_index_seek (demux, track,
|
if ((entry = gst_matroskademux_do_index_seek (demux, track,
|
||||||
seeksegment.last_stop, &demux->seek_index, &demux->seek_entry)) ==
|
seeksegment.last_stop, &demux->seek_index, &demux->seek_entry)) ==
|
||||||
NULL) {
|
NULL) {
|
||||||
|
/* pull mode without index can scan later on */
|
||||||
|
if (demux->index || demux->streaming) {
|
||||||
GST_DEBUG_OBJECT (demux, "No matching seek entry in index");
|
GST_DEBUG_OBJECT (demux, "No matching seek entry in index");
|
||||||
GST_OBJECT_UNLOCK (demux);
|
GST_OBJECT_UNLOCK (demux);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
GST_DEBUG_OBJECT (demux, "Seek position looks sane");
|
GST_DEBUG_OBJECT (demux, "Seek position looks sane");
|
||||||
GST_OBJECT_UNLOCK (demux);
|
GST_OBJECT_UNLOCK (demux);
|
||||||
|
|
||||||
|
@ -2394,6 +2618,25 @@ gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux,
|
||||||
GST_DEBUG_OBJECT (demux, "Waiting for streaming to stop");
|
GST_DEBUG_OBJECT (demux, "Waiting for streaming to stop");
|
||||||
GST_PAD_STREAM_LOCK (demux->sinkpad);
|
GST_PAD_STREAM_LOCK (demux->sinkpad);
|
||||||
|
|
||||||
|
/* pull mode without index can do some scanning */
|
||||||
|
if (!demux->streaming && !demux->index) {
|
||||||
|
/* need to stop flushing upstream as we need it next */
|
||||||
|
if (flush)
|
||||||
|
gst_pad_push_event (demux->sinkpad, gst_event_new_flush_stop ());
|
||||||
|
entry = gst_matroska_demux_search_pos (demux, seeksegment.last_stop);
|
||||||
|
/* keep local copy */
|
||||||
|
if (entry) {
|
||||||
|
scan_entry = *entry;
|
||||||
|
g_free (entry);
|
||||||
|
entry = &scan_entry;
|
||||||
|
} else {
|
||||||
|
GST_DEBUG_OBJECT (demux, "Scan failed to find matching position");
|
||||||
|
if (flush)
|
||||||
|
gst_matroska_demux_send_event (demux, gst_event_new_flush_stop ());
|
||||||
|
goto seek_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (keyunit) {
|
if (keyunit) {
|
||||||
GST_DEBUG_OBJECT (demux, "seek to key unit, adjusting segment start to %"
|
GST_DEBUG_OBJECT (demux, "seek to key unit, adjusting segment start to %"
|
||||||
GST_TIME_FORMAT, GST_TIME_ARGS (entry->time));
|
GST_TIME_FORMAT, GST_TIME_ARGS (entry->time));
|
||||||
|
@ -5346,6 +5589,11 @@ gst_matroska_demux_parse_id (GstMatroskaDemux * demux, guint32 id,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case GST_MATROSKA_DEMUX_STATE_SCANNING:
|
||||||
|
if (id != GST_MATROSKA_ID_CLUSTER &&
|
||||||
|
id != GST_MATROSKA_ID_CLUSTERTIMECODE)
|
||||||
|
goto skip;
|
||||||
|
/* fall-through */
|
||||||
case GST_MATROSKA_DEMUX_STATE_HEADER:
|
case GST_MATROSKA_DEMUX_STATE_HEADER:
|
||||||
case GST_MATROSKA_DEMUX_STATE_DATA:
|
case GST_MATROSKA_DEMUX_STATE_DATA:
|
||||||
case GST_MATROSKA_DEMUX_STATE_SEEK:
|
case GST_MATROSKA_DEMUX_STATE_SEEK:
|
||||||
|
@ -5379,6 +5627,7 @@ gst_matroska_demux_parse_id (GstMatroskaDemux * demux, guint32 id,
|
||||||
}
|
}
|
||||||
if (G_UNLIKELY (demux->state == GST_MATROSKA_DEMUX_STATE_HEADER)) {
|
if (G_UNLIKELY (demux->state == GST_MATROSKA_DEMUX_STATE_HEADER)) {
|
||||||
demux->state = GST_MATROSKA_DEMUX_STATE_DATA;
|
demux->state = GST_MATROSKA_DEMUX_STATE_DATA;
|
||||||
|
demux->first_cluster_offset = demux->offset;
|
||||||
GST_DEBUG_OBJECT (demux, "signaling no more pads");
|
GST_DEBUG_OBJECT (demux, "signaling no more pads");
|
||||||
gst_element_no_more_pads (GST_ELEMENT (demux));
|
gst_element_no_more_pads (GST_ELEMENT (demux));
|
||||||
/* send initial newsegment */
|
/* send initial newsegment */
|
||||||
|
|
|
@ -46,7 +46,8 @@ typedef enum {
|
||||||
GST_MATROSKA_DEMUX_STATE_SEGMENT,
|
GST_MATROSKA_DEMUX_STATE_SEGMENT,
|
||||||
GST_MATROSKA_DEMUX_STATE_HEADER,
|
GST_MATROSKA_DEMUX_STATE_HEADER,
|
||||||
GST_MATROSKA_DEMUX_STATE_DATA,
|
GST_MATROSKA_DEMUX_STATE_DATA,
|
||||||
GST_MATROSKA_DEMUX_STATE_SEEK
|
GST_MATROSKA_DEMUX_STATE_SEEK,
|
||||||
|
GST_MATROSKA_DEMUX_STATE_SCANNING
|
||||||
} GstMatroskaDemuxState;
|
} GstMatroskaDemuxState;
|
||||||
|
|
||||||
typedef struct _GstMatroskaDemux {
|
typedef struct _GstMatroskaDemux {
|
||||||
|
@ -112,6 +113,7 @@ typedef struct _GstMatroskaDemux {
|
||||||
/* some state saving */
|
/* some state saving */
|
||||||
GstClockTime cluster_time;
|
GstClockTime cluster_time;
|
||||||
guint64 cluster_offset;
|
guint64 cluster_offset;
|
||||||
|
guint64 first_cluster_offset;
|
||||||
|
|
||||||
/* push based mode usual suspects */
|
/* push based mode usual suspects */
|
||||||
GstAdapter *adapter;
|
GstAdapter *adapter;
|
||||||
|
|
Loading…
Reference in a new issue