summaryrefslogtreecommitdiffstats
path: root/demux
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2014-01-22 22:12:14 +0100
committerwm4 <wm4@nowhere>2014-01-22 22:17:01 +0100
commit85bd178dbe95d0970d92c7721a998cee60f14d0b (patch)
treeb56165cfdae83298efc791b6265b4423e4f678b8 /demux
parent0931cff1e94009480444f6e9f26f330b9ac8a4ad (diff)
downloadmpv-85bd178dbe95d0970d92c7721a998cee60f14d0b.tar.bz2
mpv-85bd178dbe95d0970d92c7721a998cee60f14d0b.tar.xz
demux_mkv: avoid seeking when reading headers
This makes everything more robust, and also somewhat simpler (even if the diffstat isn't very impressive). Instead of recursively following SeekHeads while reading headers, just read the headers until the first cluster, and then possibly use SeekHeads to read the remaining missing headers.
Diffstat (limited to 'demux')
-rw-r--r--demux/demux_mkv.c244
1 files changed, 116 insertions, 128 deletions
diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c
index f805c24c0a..bec8f871a0 100644
--- a/demux/demux_mkv.c
+++ b/demux/demux_mkv.c
@@ -183,14 +183,12 @@ typedef struct mkv_demuxer {
bool index_complete;
uint64_t deferred_cues;
- int64_t *parsed_pos;
- int num_parsed_pos;
- bool parsed_info;
- bool parsed_tracks;
- bool parsed_tags;
- bool parsed_chapters;
- bool parsed_attachments;
- bool parsed_cues;
+ struct header_elem {
+ int32_t id;
+ int64_t pos;
+ bool parsed;
+ } *headers;
+ int num_headers;
uint64_t skip_to_timecode;
int v_skip_to_keyframe, a_skip_to_keyframe;
@@ -219,30 +217,6 @@ static void *grow_array(void *array, int nelem, size_t elsize)
return array;
}
-static bool is_parsed_header(struct mkv_demuxer *mkv_d, int64_t pos)
-{
- int low = 0;
- int high = mkv_d->num_parsed_pos;
- while (high > low + 1) {
- int mid = (high + low) >> 1;
- if (mkv_d->parsed_pos[mid] > pos) {
- high = mid;
- } else {
- low = mid;
- }
- }
- if (mkv_d->num_parsed_pos && mkv_d->parsed_pos[low] == pos)
- return true;
- if (!(mkv_d->num_parsed_pos & 31))
- mkv_d->parsed_pos = talloc_realloc(mkv_d, mkv_d->parsed_pos, int64_t,
- mkv_d->num_parsed_pos + 32);
- mkv_d->num_parsed_pos++;
- for (int i = mkv_d->num_parsed_pos - 1; i > low; i--)
- mkv_d->parsed_pos[i] = mkv_d->parsed_pos[i - 1];
- mkv_d->parsed_pos[low] = pos;
- return false;
-}
-
#define AAC_SYNC_EXTENSION_TYPE 0x02b7
static int aac_get_sample_rate_index(uint32_t sample_rate)
{
@@ -359,6 +333,8 @@ static int demux_mkv_read_info(demuxer_t *demuxer)
stream_t *s = demuxer->stream;
int res = 0;
+ MP_VERBOSE(demuxer, "|+ segment information...\n");
+
mkv_d->tc_scale = 1000000;
mkv_d->duration = 0;
@@ -669,6 +645,8 @@ static int demux_mkv_read_tracks(demuxer_t *demuxer)
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
stream_t *s = demuxer->stream;
+ MP_VERBOSE(demuxer, "|+ segment tracks...\n");
+
struct ebml_tracks tracks = {0};
struct ebml_parse_ctx parse_ctx = {demuxer->log};
if (ebml_read_element(s, &parse_ctx, &tracks, &ebml_tracks_desc) < 0)
@@ -722,6 +700,8 @@ static int demux_mkv_read_cues(demuxer_t *demuxer)
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
stream_t *s = demuxer->stream;
+ mkv_d->deferred_cues = 0;
+
if (opts->index_mode == 0 || opts->index_mode == 2) {
ebml_read_skip(demuxer->log, -1, s);
return 0;
@@ -993,6 +973,46 @@ static int demux_mkv_read_attachments(demuxer_t *demuxer)
static int read_header_element(struct demuxer *demuxer, uint32_t id,
int64_t at_filepos);
+static struct header_elem *get_header_element(struct demuxer *demuxer,
+ uint32_t id,
+ int64_t element_filepos)
+{
+ struct mkv_demuxer *mkv_d = demuxer->priv;
+
+ // Note that some files in fact contain a SEEKHEAD with a list of all
+ // clusters - we have no use for that.
+ if (!ebml_is_mkv_level1_id(id) || id == MATROSKA_ID_CLUSTER)
+ return NULL;
+
+ for (int n = 0; n < mkv_d->num_headers; n++) {
+ struct header_elem *elem = &mkv_d->headers[n];
+ // SEEKHEAD is the only element that can happen multiple times.
+ // Other elements might be duplicated (or attempted to be read twice,
+ // even if it's only once in the file), but only the first is used.
+ if (elem->id == id && (id != MATROSKA_ID_SEEKHEAD ||
+ elem->pos == element_filepos))
+ return elem;
+ }
+ struct header_elem elem = { .id = id, .pos = element_filepos };
+ MP_TARRAY_APPEND(mkv_d, mkv_d->headers, mkv_d->num_headers, elem);
+ return &mkv_d->headers[mkv_d->num_headers - 1];
+}
+
+// Mark the level 1 element with the given id as read. Return whether it
+// was marked read before (e.g. for checking whether it was already read).
+// element_filepos refers to the file position of the element ID.
+static bool test_header_element(struct demuxer *demuxer, uint32_t id,
+ int64_t element_filepos)
+{
+ struct header_elem *elem = get_header_element(demuxer, id, element_filepos);
+ if (!elem)
+ return false;
+ if (elem->parsed)
+ return true;
+ elem->parsed = true;
+ return false;
+}
+
static int demux_mkv_read_seekhead(demuxer_t *demuxer)
{
struct mkv_demuxer *mkv_d = demuxer->priv;
@@ -1006,8 +1026,6 @@ static int demux_mkv_read_seekhead(demuxer_t *demuxer)
res = -1;
goto out;
}
- /* off now holds the position of the next element after the seek head. */
- int64_t off = stream_tell(s);
for (int i = 0; i < seekhead.n_seek; i++) {
struct ebml_seek *seek = &seekhead.seek[i];
if (seek->n_seek_id != 1 || seek->n_seek_position != 1) {
@@ -1015,21 +1033,13 @@ static int demux_mkv_read_seekhead(demuxer_t *demuxer)
continue;
}
uint64_t pos = seek->seek_position + mkv_d->segment_start;
- if (pos >= s->end_pos) {
+ MP_VERBOSE(demuxer, "Element 0x%x at %"PRIu64".\n",
+ (unsigned)seek->seek_id, pos);
+ get_header_element(demuxer, seek->seek_id, pos);
+ // This is nice to warn against incomplete files.
+ if (pos >= s->end_pos)
MP_WARN(demuxer, "SeekHead position beyond "
- "end of file - incomplete file?\n");
- continue;
- }
- int r = read_header_element(demuxer, seek->seek_id, pos);
- if (r <= -2) {
- res = r;
- goto out;
- }
- }
- if (!stream_seek(s, off)) {
- MP_ERR(demuxer, "Couldn't seek back after "
- "SeekHead??\n");
- res = -1;
+ "end of file - incomplete file?\n");
}
out:
MP_VERBOSE(demuxer, "\\---- [ parsing seek head ] ---------\n");
@@ -1037,99 +1047,34 @@ static int demux_mkv_read_seekhead(demuxer_t *demuxer)
return res;
}
-static bool seek_pos_id(struct demuxer *demuxer, int64_t pos, uint32_t id)
-{
- stream_t *s = demuxer->stream;
- if (!stream_seek(s, pos)) {
- MP_WARN(demuxer, "Failed to seek in file\n");
- return false;
- }
- if (ebml_read_id(s) != id) {
- MP_WARN(demuxer, "Expected element not found\n");
- return false;
- }
- return true;
-}
-
static int read_header_element(struct demuxer *demuxer, uint32_t id,
- int64_t at_filepos)
+ int64_t start_filepos)
{
- struct mkv_demuxer *mkv_d = demuxer->priv;
- stream_t *s = demuxer->stream;
- int64_t pos = stream_tell(s) - 4;
- int res = 1;
+ if (id == EBML_ID_INVALID)
+ return 0;
+
+ if (test_header_element(demuxer, id, start_filepos))
+ goto skip;
switch(id) {
case MATROSKA_ID_INFO:
- if (mkv_d->parsed_info)
- break;
- if (at_filepos && !seek_pos_id(demuxer, at_filepos, id))
- return -1;
- MP_VERBOSE(demuxer, "|+ segment information...\n");
- mkv_d->parsed_info = true;
return demux_mkv_read_info(demuxer);
-
case MATROSKA_ID_TRACKS:
- if (mkv_d->parsed_tracks)
- break;
- if (at_filepos && !seek_pos_id(demuxer, at_filepos, id))
- return -1;
- mkv_d->parsed_tracks = true;
- MP_VERBOSE(demuxer, "|+ segment tracks...\n");
return demux_mkv_read_tracks(demuxer);
-
case MATROSKA_ID_CUES:
- if (mkv_d->parsed_cues)
- break;
- if (at_filepos) {
- // Read cues when they are needed, to avoid seeking on opening.
- mkv_d->deferred_cues = at_filepos;
- return 1;
- }
- mkv_d->parsed_cues = true;
- mkv_d->deferred_cues = 0;
return demux_mkv_read_cues(demuxer);
-
case MATROSKA_ID_TAGS:
- if (mkv_d->parsed_tags)
- break;
- if (at_filepos && !seek_pos_id(demuxer, at_filepos, id))
- return -1;
- mkv_d->parsed_tags = true;
return demux_mkv_read_tags(demuxer);
-
case MATROSKA_ID_SEEKHEAD:
- if (is_parsed_header(mkv_d, pos))
- break;
- if (at_filepos && !seek_pos_id(demuxer, at_filepos, id))
- return -1;
return demux_mkv_read_seekhead(demuxer);
-
case MATROSKA_ID_CHAPTERS:
- if (mkv_d->parsed_chapters)
- break;
- if (at_filepos && !seek_pos_id(demuxer, at_filepos, id))
- return -1;
- mkv_d->parsed_chapters = true;
return demux_mkv_read_chapters(demuxer);
-
case MATROSKA_ID_ATTACHMENTS:
- if (mkv_d->parsed_attachments)
- break;
- if (at_filepos && !seek_pos_id(demuxer, at_filepos, id))
- return -1;
- mkv_d->parsed_attachments = true;
return demux_mkv_read_attachments(demuxer);
-
- case EBML_ID_VOID:
- break;
-
- default:
- res = 2;
}
- if (!at_filepos && id != EBML_ID_INVALID)
- ebml_read_skip(demuxer->log, -1, s);
- return res;
+skip:
+ ebml_read_skip(demuxer->log, -1, demuxer->stream);
+ return 0;
}
@@ -1787,8 +1732,7 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
{
stream_t *s = demuxer->stream;
mkv_demuxer_t *mkv_d;
-
- stream_seek(s, s->start_pos);
+ int64_t start_pos;
if (!read_ebml_header(demuxer))
return -1;
@@ -1806,23 +1750,67 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
*demuxer->params->matroska_was_valid = true;
while (1) {
+ start_pos = stream_tell(s);
+ stream_peek(s, 4); // make sure we can always seek back
uint32_t id = ebml_read_id(s);
if (s->eof) {
MP_WARN(demuxer, "Unexpected end of file (no clusters found)\n");
break;
}
if (id == MATROSKA_ID_CLUSTER) {
- MP_VERBOSE(demuxer, "|+ found cluster, headers are "
- "parsed completely :)\n");
- stream_seek(s, stream_tell(s) - 4);
+ MP_VERBOSE(demuxer, "|+ found cluster\n");
break;
}
- int res = read_header_element(demuxer, id, 0);
+ int res = read_header_element(demuxer, id, start_pos);
+ if (res <= -2)
+ return -1;
+ if (res < 0)
+ break;
+ }
+
+ // Read headers that come after the first cluster (i.e. require seeking).
+ // Note: reading might increase ->num_headers.
+ // Likewise, ->headers might be reallocated.
+ for (int n = 0; n < mkv_d->num_headers; n++) {
+ struct header_elem *elem = &mkv_d->headers[n];
+ if (elem->parsed)
+ continue;
+ elem->parsed = true;
+ if (elem->id == MATROSKA_ID_CUES) {
+ // Read cues when they are needed, to avoid seeking on opening.
+ MP_VERBOSE(demuxer, "Deferring reading cues.\n");
+ mkv_d->deferred_cues = elem->pos;
+ continue;
+ }
+ MP_VERBOSE(demuxer, "Seeking to %"PRIu64" to read header element 0x%x.\n",
+ elem->pos, (unsigned)elem->id);
+ if (elem->pos >= s->end_pos) {
+ MP_WARN(demuxer, "SeekHead position beyond "
+ "end of file - incomplete file?\n");
+ continue;
+ }
+ if (!stream_seek(s, elem->pos)) {
+ MP_WARN(demuxer, "Failed to seek when reading header element.\n");
+ continue;
+ }
+ if (ebml_read_id(s) != elem->id) {
+ MP_ERR(demuxer, "Expected element 0x%x not found\n",
+ (unsigned int)elem->id);
+ continue;
+ }
+ elem->parsed = false; // don't make read_header_element skip it
+ int res = read_header_element(demuxer, elem->id, elem->pos);
if (res <= -2)
return -1;
if (res < 0)
break;
}
+ if (!stream_seek(s, start_pos)) {
+ MP_ERR(demuxer, "Couldn't seek back after reading headers?\n");
+ return -1;
+ }
+
+ MP_VERBOSE(demuxer, "All headers are parsed!\n");
display_create_tracks(demuxer);