summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2013-04-14 02:49:07 +0200
committerwm4 <wm4@nowhere>2013-04-20 23:28:23 +0200
commit8b017c73c4ac3d54bf4d16d8349059c1de4dbc36 (patch)
tree8c0f01a12a3215ff974cdfbbd1b715e749cbd30f
parentf989b6081b38d4b05e38ec879e0364694eb08152 (diff)
downloadmpv-8b017c73c4ac3d54bf4d16d8349059c1de4dbc36.tar.bz2
mpv-8b017c73c4ac3d54bf4d16d8349059c1de4dbc36.tar.xz
core: matroska: support concatenated segments
Matroska files can contain multiple segments, which are literally further Matroska files appended to the main file. They can be referenced by segment linking. While this is an extraordinarily useless and dumb feature, we support it for the hell of it. This is implemented by adding a further demuxer parameter for skipping segments. When scanning for linked segments, each file is opened multiple times, until there are no further segments found. Each segment will have a separate demuxer instance (with a separate file handle etc.). It appears the Matroska spec. has an even worse feature for segments: live streaming can completely reconfigure the stream by starting a new segment. We won't add support for it, because there are 0 people on this earth who think Matroska life streaming is a good idea. (As opposed to serving Matroska/WebM files via HTTP.)
-rw-r--r--core/timeline/tl_matroska.c123
-rw-r--r--demux/demux.h2
-rw-r--r--demux/demux_mkv.c61
3 files changed, 133 insertions, 53 deletions
diff --git a/core/timeline/tl_matroska.c b/core/timeline/tl_matroska.c
index 75530116aa..98ba635bec 100644
--- a/core/timeline/tl_matroska.c
+++ b/core/timeline/tl_matroska.c
@@ -114,16 +114,15 @@ static char **find_files(const char *original_file, const char *suffix)
}
static struct demuxer *open_demuxer(struct stream *stream,
- struct MPContext *mpctx, char *filename, unsigned char uid_map[][16])
+ struct MPContext *mpctx, char *filename, struct demuxer_params *params)
{
return demux_open_withparams(&mpctx->opts, stream,
DEMUXER_TYPE_MATROSKA, NULL, mpctx->opts.audio_id,
- mpctx->opts.video_id, mpctx->opts.sub_id, filename,
- &(struct demuxer_params){.matroska_wanted_uids = uid_map});
+ mpctx->opts.video_id, mpctx->opts.sub_id, filename, params);
}
static int enable_cache(struct MPContext *mpctx, struct stream **stream,
- struct demuxer **demuxer, unsigned char uid_map[][16])
+ struct demuxer **demuxer, struct demuxer_params *params)
{
struct MPOpts *opts = &mpctx->opts;
@@ -147,7 +146,7 @@ static int enable_cache(struct MPContext *mpctx, struct stream **stream,
opts->stream_cache_min_percent,
opts->stream_cache_seek_min_percent);
- *demuxer = open_demuxer(*stream, mpctx, filename, uid_map);
+ *demuxer = open_demuxer(*stream, mpctx, filename, params);
if (!*demuxer) {
talloc_free(filename);
free_stream(*stream);
@@ -158,6 +157,68 @@ static int enable_cache(struct MPContext *mpctx, struct stream **stream,
return 1;
}
+// segment = get Nth segment of a multi-segment file
+static bool check_file_seg(struct MPContext *mpctx, struct demuxer **sources,
+ int num_sources, unsigned char uid_map[][16],
+ char *filename, int segment)
+{
+ bool was_valid = false;
+ struct demuxer_params params = {
+ .matroska_wanted_uids = uid_map,
+ .matroska_wanted_segment = segment,
+ .matroska_was_valid = &was_valid,
+ };
+ int format = 0;
+ struct stream *s = open_stream(filename, &mpctx->opts, &format);
+ if (!s)
+ return false;
+ struct demuxer *d = open_demuxer(s, mpctx, filename, &params);
+
+ if (!d) {
+ free_stream(s);
+ return was_valid;
+ }
+ if (d->file_format == DEMUXER_TYPE_MATROSKA) {
+ for (int i = 1; i < num_sources; i++) {
+ if (sources[i])
+ continue;
+ if (!memcmp(uid_map[i], d->matroska_data.segment_uid, 16)) {
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "Match for source %d: %s\n",
+ i, d->filename);
+
+ if (enable_cache(mpctx, &s, &d, &params) < 0)
+ continue;
+
+ sources[i] = d;
+ return true;
+ }
+ }
+ }
+ free_demuxer(d);
+ free_stream(s);
+ return was_valid;
+}
+
+static void check_file(struct MPContext *mpctx, struct demuxer **sources,
+ int num_sources, unsigned char uid_map[][16],
+ char *filename, int first)
+{
+ for (int segment = first; ; segment++) {
+ if (!check_file_seg(mpctx, sources, num_sources, uid_map,
+ filename, segment))
+ break;
+ }
+}
+
+static bool missing(struct demuxer **sources, int num_sources)
+{
+ for (int i = 0; i < num_sources; i++) {
+ if (!sources[i])
+ return true;
+ }
+ return false;
+}
+
static int find_ordered_chapter_sources(struct MPContext *mpctx,
struct demuxer **sources,
int num_sources,
@@ -166,6 +227,7 @@ static int find_ordered_chapter_sources(struct MPContext *mpctx,
int num_filenames = 0;
char **filenames = NULL;
if (num_sources > 1) {
+ char *main_filename = mpctx->demuxer->filename;
mp_msg(MSGT_CPLAYER, MSGL_INFO, "This file references data from "
"other sources.\n");
if (mpctx->demuxer->stream->type != STREAMTYPE_FILE) {
@@ -174,59 +236,34 @@ static int find_ordered_chapter_sources(struct MPContext *mpctx,
} else {
mp_msg(MSGT_CPLAYER, MSGL_INFO, "Will scan other files in the "
"same directory to find referenced sources.\n");
- filenames = find_files(mpctx->demuxer->filename, ".mkv");
+ filenames = find_files(main_filename, ".mkv");
num_filenames = MP_TALLOC_ELEMS(filenames);
}
+ // Possibly get further segments appended to the first segment
+ check_file(mpctx, sources, num_sources, uid_map, main_filename, 1);
}
- int num_left = num_sources - 1;
- for (int i = 0; i < num_filenames && num_left > 0; i++) {
+ for (int i = 0; i < num_filenames; i++) {
+ if (!missing(sources, num_sources))
+ break;
mp_msg(MSGT_CPLAYER, MSGL_INFO, "Checking file %s\n", filenames[i]);
- int format = 0;
- struct stream *s = open_stream(filenames[i], &mpctx->opts, &format);
- if (!s)
- continue;
- struct demuxer *d = open_demuxer(s, mpctx, filenames[i], uid_map);
-
- if (!d) {
- free_stream(s);
- continue;
- }
- if (d->file_format == DEMUXER_TYPE_MATROSKA) {
- for (int i = 1; i < num_sources; i++) {
- if (sources[i])
- continue;
- if (!memcmp(uid_map[i], d->matroska_data.segment_uid, 16)) {
- mp_msg(MSGT_CPLAYER, MSGL_INFO,"Match for source %d: %s\n",
- i, d->filename);
-
- if (enable_cache(mpctx, &s, &d, uid_map) < 0)
- continue;
-
- sources[i] = d;
- num_left--;
- goto match;
- }
- }
- }
- free_demuxer(d);
- free_stream(s);
- continue;
- match:
- ;
+ check_file(mpctx, sources, num_sources, uid_map, filenames[i], 0);
}
+
talloc_free(filenames);
- if (num_left) {
+ if (missing(sources, num_sources)) {
mp_msg(MSGT_CPLAYER, MSGL_ERR, "Failed to find ordered chapter part!\n"
"There will be parts MISSING from the video!\n");
- for (int i = 1, j = 1; i < num_sources; i++)
+ int j = 1;
+ for (int i = 1; i < num_sources; i++)
if (sources[i]) {
sources[j] = sources[i];
memcpy(uid_map[j], uid_map[i], 16);
j++;
}
+ num_sources = j;
}
- return num_sources - num_left;
+ return num_sources;
}
void build_ordered_chapter_timeline(struct MPContext *mpctx)
diff --git a/demux/demux.h b/demux/demux.h
index c922dc8bb5..fb695e6a45 100644
--- a/demux/demux.h
+++ b/demux/demux.h
@@ -211,6 +211,8 @@ typedef struct demux_attachment
struct demuxer_params {
unsigned char (*matroska_wanted_uids)[16];
+ int matroska_wanted_segment;
+ bool *matroska_was_valid;
};
typedef struct demuxer {
diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c
index 00f5a1a722..bd6e725251 100644
--- a/demux/demux_mkv.c
+++ b/demux/demux_mkv.c
@@ -1656,13 +1656,10 @@ static void mkv_free(struct demuxer *demuxer)
free(mkv_d->indexes);
}
-static int demux_mkv_open(demuxer_t *demuxer)
+static int read_ebml_header(demuxer_t *demuxer)
{
stream_t *s = demuxer->stream;
- mkv_demuxer_t *mkv_d;
- mkv_track_t *track;
- stream_seek(s, s->start_pos);
if (ebml_read_id(s, NULL) != EBML_ID_EBML)
return 0;
struct ebml_ebml ebml_master = {};
@@ -1698,21 +1695,65 @@ static int demux_mkv_open(demuxer_t *demuxer)
}
talloc_free(parse_ctx.talloc_ctx);
- mp_msg(MSGT_DEMUX, MSGL_V, "[mkv] Found the head...\n");
+ return 1;
+}
- if (ebml_read_id(s, NULL) != MATROSKA_ID_SEGMENT) {
- mp_msg(MSGT_DEMUX, MSGL_V, "[mkv] but no segment :(\n");
- return 0;
+static int read_mkv_segment_header(demuxer_t *demuxer)
+{
+ stream_t *s = demuxer->stream;
+ int num_skip = 0;
+ if (demuxer->params)
+ num_skip = demuxer->params->matroska_wanted_segment;
+
+ while (!s->eof) {
+ if (ebml_read_id(s, NULL) != MATROSKA_ID_SEGMENT) {
+ mp_msg(MSGT_DEMUX, MSGL_V, "[mkv] segment not found\n");
+ return 0;
+ }
+ mp_msg(MSGT_DEMUX, MSGL_V, "[mkv] + a segment...\n");
+ uint64_t len = ebml_read_length(s, NULL);
+ if (num_skip <= 0)
+ return 1;
+ num_skip--;
+ mp_msg(MSGT_DEMUX, MSGL_V, "[mkv] (skipping)\n");
+ if (len == EBML_UINT_INVALID)
+ break;
+ if (!stream_seek(s, stream_tell(s) + len)) {
+ mp_msg(MSGT_DEMUX, MSGL_WARN, "[mkv] Failed to seek in file\n");
+ return 0;
+ }
+ // Segments are like concatenated Matroska files
+ if (!read_ebml_header(demuxer))
+ return 0;
}
- ebml_read_length(s, NULL); /* return bytes number until EOF */
- mp_msg(MSGT_DEMUX, MSGL_V, "[mkv] + a segment...\n");
+ mp_msg(MSGT_DEMUX, MSGL_V, "[mkv] End of file, no further segments.\n");
+ return 0;
+}
+
+static int demux_mkv_open(demuxer_t *demuxer)
+{
+ stream_t *s = demuxer->stream;
+ mkv_demuxer_t *mkv_d;
+ mkv_track_t *track;
+
+ stream_seek(s, s->start_pos);
+
+ if (!read_ebml_header(demuxer))
+ return 0;
+ mp_msg(MSGT_DEMUX, MSGL_V, "[mkv] Found the head...\n");
+
+ if (!read_mkv_segment_header(demuxer))
+ return 0;
mkv_d = talloc_zero(demuxer, struct mkv_demuxer);
demuxer->priv = mkv_d;
mkv_d->tc_scale = 1000000;
mkv_d->segment_start = stream_tell(s);
+ if (demuxer->params && demuxer->params->matroska_was_valid)
+ *demuxer->params->matroska_was_valid = true;
+
while (1) {
uint32_t id = ebml_read_id(s, NULL);
if (s->eof) {