summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/edl-mpv.rst27
-rw-r--r--demux/demux_edl.c59
-rw-r--r--demux/demux_timeline.c378
-rw-r--r--demux/timeline.c2
-rw-r--r--demux/timeline.h8
5 files changed, 315 insertions, 159 deletions
diff --git a/DOCS/edl-mpv.rst b/DOCS/edl-mpv.rst
index 0563c28b0c..cfec52c1bc 100644
--- a/DOCS/edl-mpv.rst
+++ b/DOCS/edl-mpv.rst
@@ -131,6 +131,33 @@ The current implementation will
- not add segment boundaries as chapter points
- require full compatibility between all segments (same codec etc.)
+Separate files for tracks
+=========================
+
+The special ``new_stream`` header lets you specify separate parts and time
+offsets for separate tracks. This can for example be used to source audio and
+video track from separate files.
+
+Example::
+
+ # mpv EDL v0
+ video.mkv
+ !new_stream
+ audio.mkv
+
+This adds all tracks from both files to the virtual track list. Upon playback,
+the tracks will be played at the same time, instead of appending them. The files
+can contain more than 1 stream; the apparent effect is the same as if the second
+part after the ``!new_stream`` part were in a separate ``.edl`` file and added
+with ``--external-file``.
+
+Note that all metadata between the stream sets created by ``new_stream`` is
+disjoint. Global metadata is taken from the first part only.
+
+In context of mpv, this is redundant to the ``--audio-file`` and
+``--external-file`` options, but (as of this writing) has the advantage that
+this will use a unified cache for all streams.
+
Timestamp format
================
diff --git a/demux/demux_edl.c b/demux/demux_edl.c
index c0225737e1..52b36ebfc9 100644
--- a/demux/demux_edl.c
+++ b/demux/demux_edl.c
@@ -49,6 +49,7 @@ struct tl_parts {
char *init_fragment_url;
struct tl_part *parts;
int num_parts;
+ struct tl_parts *next;
};
struct priv {
@@ -79,6 +80,7 @@ static bool parse_time(bstr str, double *out_time)
static struct tl_parts *parse_edl(bstr str)
{
struct tl_parts *tl = talloc_zero(NULL, struct tl_parts);
+ struct tl_parts *root = tl;
while (str.len) {
if (bstr_eatstart0(&str, "#")) {
bstr_split_tok(str, "\n", &(bstr){0}, &str);
@@ -138,13 +140,15 @@ static struct tl_parts *parse_edl(bstr str)
break;
}
if (is_header) {
- if (tl->num_parts)
- goto error; // can't have header once an entry was defined
bstr type = param_vals[0]; // value, because no "="
if (bstr_equals0(type, "mp4_dash")) {
tl->dash = true;
if (nparam > 1 && bstr_equals0(param_names[1], "init"))
tl->init_fragment_url = bstrto0(tl, param_vals[1]);
+ } else if (bstr_equals0(type, "new_stream")) {
+ struct tl_parts *ntl = talloc_zero(tl, struct tl_parts);
+ tl->next = ntl;
+ tl = ntl;
}
continue;
}
@@ -152,11 +156,11 @@ static struct tl_parts *parse_edl(bstr str)
goto error;
MP_TARRAY_APPEND(tl, tl->parts, tl->num_parts, p);
}
- if (!tl->num_parts)
+ if (!root->num_parts)
goto error;
- return tl;
+ return root;
error:
- talloc_free(tl);
+ talloc_free(root);
return NULL;
}
@@ -260,11 +264,12 @@ static void build_timeline(struct timeline *tl, struct tl_parts *parts)
MP_WARN(tl, "Segment %d has unknown duration.\n", n);
if (part->offset_set)
MP_WARN(tl, "Offsets are ignored.\n");
- tl->demuxer->is_network = true;
+ if (tl->demuxer)
+ tl->demuxer->is_network = true;
if (!tl->track_layout) {
- source = open_source(tl, part->filename);
- if (!source)
+ tl->track_layout = open_source(tl, part->filename);
+ if (!tl->track_layout)
goto error;
}
} else {
@@ -320,12 +325,8 @@ static void build_timeline(struct timeline *tl, struct tl_parts *parts)
starttime += part->length;
- if (source) {
- tl->demuxer->is_network |= source->is_network;
-
- if (!tl->track_layout)
- tl->track_layout = source;
- }
+ if (source && !tl->track_layout)
+ tl->track_layout = source;
}
tl->parts[parts->num_parts] = (struct timeline_part) {.start = starttime};
tl->num_parts = parts->num_parts;
@@ -352,16 +353,32 @@ static void build_mpv_edl_timeline(struct timeline *tl)
{
struct priv *p = tl->demuxer->priv;
- struct tl_parts *parts = parse_edl(p->data);
- if (!parts) {
+ struct timeline *root_tl = tl;
+ struct tl_parts *root = parse_edl(p->data);
+ if (!root) {
MP_ERR(tl, "Error in EDL.\n");
return;
}
- MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, tl->demuxer);
- if (!p->allow_any)
- fix_filenames(parts, tl->demuxer->filename);
- build_timeline(tl, parts);
- talloc_free(parts);
+
+ for (struct tl_parts *parts = root; parts; parts = parts->next) {
+ if (tl->demuxer)
+ MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, tl->demuxer);
+ if (!p->allow_any)
+ fix_filenames(parts, root_tl->demuxer->filename);
+ build_timeline(tl, parts);
+
+ if (parts->next) {
+ struct timeline *ntl = talloc_zero(tl, struct timeline);
+ *ntl = (struct timeline) {
+ .global = tl->global,
+ .log = tl->log,
+ .cancel = tl->cancel,
+ };
+ tl->next = ntl;
+ tl = ntl;
+ }
+ }
+ talloc_free(root);
}
static int try_open_file(struct demuxer *demuxer, enum demux_check check)
diff --git a/demux/demux_timeline.c b/demux/demux_timeline.c
index 49046222d9..3c5304c6dc 100644
--- a/demux/demux_timeline.c
+++ b/demux/demux_timeline.c
@@ -27,17 +27,18 @@
#include "stream/stream.h"
struct segment {
- int index;
+ int index; // index into virtual_source.segments[] (and timeline.parts[])
double start, end;
double d_start;
char *url;
bool lazy;
struct demuxer *d;
- // stream_map[sh_stream.index] = index into priv.streams, where sh_stream
- // is a stream from the source d. It's used to map the streams of the
+ // stream_map[sh_stream.index] = virtual_stream, where sh_stream is a stream
+ // from the source d, and virtual_stream is a streamexported by the
+ // timeline demuxer (virtual_stream.sh). It's used to map the streams of the
// source onto the set of streams of the virtual timeline.
- // Uses -1 for streams that do not appear in the virtual timeline.
- int *stream_map;
+ // Uses NULL for streams that do not appear in the virtual timeline.
+ struct virtual_stream **stream_map;
int num_stream_map;
};
@@ -47,68 +48,87 @@ struct virtual_stream {
struct sh_stream *sh; // stream exported by demux_timeline
bool selected; // ==demux_stream_is_selected(sh)
int eos_packets; // deal with b-frame delay
+ struct virtual_source *src; // group this stream is part of
};
-struct priv {
+// This represents a single timeline source. (See timeline.next. For each
+// timeline struct there is a virtual_source.)
+struct virtual_source {
struct timeline *tl;
- double duration;
bool dash;
struct segment **segments;
int num_segments;
struct segment *current;
- // As the demuxer user sees it.
struct virtual_stream **streams;
int num_streams;
// Total number of packets received past end of segment. Used
// to be clever about determining when to switch segments.
int eos_packets;
+
+ bool eof_reached;
+ double dts; // highest read DTS (or PTS if no DTS available)
+ bool any_selected; // at least one stream is actually selected
+};
+
+struct priv {
+ struct timeline *tl;
+
+ double duration;
+
+ // As the demuxer user sees it.
+ struct virtual_stream **streams;
+ int num_streams;
+
+ struct virtual_source **sources;
+ int num_sources;
};
-static bool target_stream_used(struct segment *seg, int target_index)
+static void add_tl(struct demuxer *demuxer, struct timeline *tl);
+
+static bool target_stream_used(struct segment *seg, struct virtual_stream *vs)
{
for (int n = 0; n < seg->num_stream_map; n++) {
- if (seg->stream_map[n] == target_index)
+ if (seg->stream_map[n] == vs)
return true;
}
return false;
}
// Create mapping from segment streams to virtual timeline streams.
-static void associate_streams(struct demuxer *demuxer, struct segment *seg)
+static void associate_streams(struct demuxer *demuxer,
+ struct virtual_source *src,
+ struct segment *seg)
{
- struct priv *p = demuxer->priv;
-
if (!seg->d || seg->stream_map)
return;
- int counts[STREAM_TYPE_COUNT] = {0};
-
int num_streams = demux_get_num_stream(seg->d);
for (int n = 0; n < num_streams; n++) {
struct sh_stream *sh = demux_get_stream(seg->d, n);
- // Try associating by demuxer ID (supposedly useful for ordered chapters).
- struct sh_stream *other =
- demuxer_stream_by_demuxer_id(demuxer, sh->type, sh->demuxer_id);
- if (!other || !target_stream_used(seg, other->index)) {
- // Try to associate the first unused stream with matching media type.
- for (int i = 0; i < p->num_streams; i++) {
- struct sh_stream *cur = p->streams[i]->sh;
- if (cur->type == sh->type && !target_stream_used(seg, cur->index))
- {
- other = cur;
- break;
- }
- }
- }
+ struct virtual_stream *other = NULL;
+ for (int i = 0; i < src->num_streams; i++) {
+ struct virtual_stream *vs = src->streams[i];
+
+ // The stream must always have the same media type. Also, a stream
+ // can't be assigned multiple times.
+ if (sh->type != vs->sh->type || target_stream_used(seg, vs))
+ continue;
+
+ // By default pick the first matching stream.
+ if (!other)
+ other = vs;
- MP_TARRAY_APPEND(seg, seg->stream_map, seg->num_stream_map,
- other ? other->index : -1);
+ // Matching by demuxer ID is supposedly useful and preferable for
+ // ordered chapters.
+ if (sh->demuxer_id == vs->sh->demuxer_id)
+ other = vs;
+ }
- counts[sh->type] += 1;
+ MP_TARRAY_APPEND(seg, seg->stream_map, seg->num_stream_map, other);
}
}
@@ -121,86 +141,100 @@ static void reselect_streams(struct demuxer *demuxer)
vs->selected = demux_stream_is_selected(vs->sh);
}
- for (int n = 0; n < p->num_segments; n++) {
- struct segment *seg = p->segments[n];
- for (int i = 0; i < seg->num_stream_map; i++) {
+ for (int x = 0; x < p->num_sources; x++) {
+ struct virtual_source *src = p->sources[x];
+
+ for (int n = 0; n < src->num_segments; n++) {
+ struct segment *seg = src->segments[n];
+
if (!seg->d)
continue;
- struct sh_stream *sh = demux_get_stream(seg->d, i);
- bool selected = false;
- if (seg->stream_map[i] >= 0)
- selected = p->streams[seg->stream_map[i]]->selected;
- // This stops demuxer readahead for inactive segments.
- if (!p->current || seg->d != p->current->d)
- selected = false;
- demuxer_select_track(seg->d, sh, MP_NOPTS_VALUE, selected);
+ for (int i = 0; i < seg->num_stream_map; i++) {
+ bool selected =
+ seg->stream_map[i] && seg->stream_map[i]->selected;
+
+ // This stops demuxer readahead for inactive segments.
+ if (!src->current || seg->d != src->current->d)
+ selected = false;
+ struct sh_stream *sh = demux_get_stream(seg->d, i);
+ demuxer_select_track(seg->d, sh, MP_NOPTS_VALUE, selected);
+ }
+ }
+
+ bool was_selected = src->any_selected;
+ src->any_selected = false;
+
+ for (int n = 0; n < src->num_streams; n++)
+ src->any_selected |= src->streams[n]->selected;
+
+ if (!was_selected && src->any_selected) {
+ src->eof_reached = false;
+ src->dts = MP_NOPTS_VALUE;
}
}
}
-static void close_lazy_segments(struct demuxer *demuxer)
+static void close_lazy_segments(struct demuxer *demuxer,
+ struct virtual_source *src)
{
- struct priv *p = demuxer->priv;
-
// unload previous segment
- for (int n = 0; n < p->num_segments; n++) {
- struct segment *seg = p->segments[n];
- if (seg != p->current && seg->d && seg->lazy) {
+ for (int n = 0; n < src->num_segments; n++) {
+ struct segment *seg = src->segments[n];
+ if (seg != src->current && seg->d && seg->lazy) {
demux_free(seg->d);
seg->d = NULL;
}
}
}
-static void reopen_lazy_segments(struct demuxer *demuxer)
+static void reopen_lazy_segments(struct demuxer *demuxer,
+ struct virtual_source *src)
{
- struct priv *p = demuxer->priv;
-
- if (p->current->d)
+ if (src->current->d)
return;
- close_lazy_segments(demuxer);
+ close_lazy_segments(demuxer, src);
struct demuxer_params params = {
- .init_fragment = p->tl->init_fragment,
+ .init_fragment = src->tl->init_fragment,
.skip_lavf_probing = true,
};
- p->current->d = demux_open_url(p->current->url, &params,
- demuxer->cancel, demuxer->global);
- if (!p->current->d && !demux_cancel_test(demuxer))
+ src->current->d = demux_open_url(src->current->url, &params,
+ demuxer->cancel, demuxer->global);
+ if (!src->current->d && !demux_cancel_test(demuxer))
MP_ERR(demuxer, "failed to load segment\n");
- if (p->current->d)
- demux_disable_cache(p->current->d);
- associate_streams(demuxer, p->current);
+ if (src->current->d)
+ demux_disable_cache(src->current->d);
+ associate_streams(demuxer, src, src->current);
}
-static void switch_segment(struct demuxer *demuxer, struct segment *new,
- double start_pts, int flags, bool init)
+static void switch_segment(struct demuxer *demuxer, struct virtual_source *src,
+ struct segment *new, double start_pts, int flags,
+ bool init)
{
- struct priv *p = demuxer->priv;
-
if (!(flags & SEEK_FORWARD))
flags |= SEEK_HR;
MP_VERBOSE(demuxer, "switch to segment %d\n", new->index);
- p->current = new;
- reopen_lazy_segments(demuxer);
+ src->current = new;
+ reopen_lazy_segments(demuxer, src);
if (!new->d)
return;
reselect_streams(demuxer);
- if (!p->dash)
+ if (!src->dash)
demux_set_ts_offset(new->d, new->start - new->d_start);
- if (!p->dash || !init)
+ if (!src->dash || !init)
demux_seek(new->d, start_pts, flags);
- for (int n = 0; n < p->num_streams; n++) {
- struct virtual_stream *vs = p->streams[n];
+ for (int n = 0; n < src->num_streams; n++) {
+ struct virtual_stream *vs = src->streams[n];
vs->eos_packets = 0;
}
- p->eos_packets = 0;
+ src->eof_reached = false;
+ src->eos_packets = 0;
}
static void d_seek(struct demuxer *demuxer, double seek_pts, int flags)
@@ -211,40 +245,64 @@ static void d_seek(struct demuxer *demuxer, double seek_pts, int flags)
flags &= SEEK_FORWARD | SEEK_HR;
- struct segment *new = p->segments[p->num_segments - 1];
- for (int n = 0; n < p->num_segments; n++) {
- if (pts < p->segments[n]->end) {
- new = p->segments[n];
- break;
+ for (int x = 0; x < p->num_sources; x++) {
+ struct virtual_source *src = p->sources[x];
+
+ struct segment *new = src->segments[src->num_segments - 1];
+ for (int n = 0; n < src->num_segments; n++) {
+ if (pts < src->segments[n]->end) {
+ new = src->segments[n];
+ break;
+ }
}
- }
- switch_segment(demuxer, new, pts, flags, false);
+ switch_segment(demuxer, src, new, pts, flags, false);
+
+ src->dts = MP_NOPTS_VALUE;
+ }
}
static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt)
{
struct priv *p = demuxer->priv;
- if (!p->current)
- switch_segment(demuxer, p->segments[0], 0, 0, true);
+ struct virtual_source *src = NULL;
+
+ for (int x = 0; x < p->num_sources; x++) {
+ struct virtual_source *cur = p->sources[x];
+
+ if (!cur->any_selected || cur->eof_reached)
+ continue;
+
+ if (!cur->current)
+ switch_segment(demuxer, cur, cur->segments[0], 0, 0, true);
- struct segment *seg = p->current;
- if (!seg || !seg->d)
+ if (!cur->any_selected || !cur->current || !cur->current->d)
+ continue;
+
+ if (!src || cur->dts == MP_NOPTS_VALUE ||
+ (src->dts != MP_NOPTS_VALUE && cur->dts < src->dts))
+ src = cur;
+ }
+
+ if (!src)
return false;
+ struct segment *seg = src->current;
+ assert(seg && seg->d);
+
struct demux_packet *pkt = demux_read_any_packet(seg->d);
if (!pkt || pkt->pts >= seg->end)
- p->eos_packets += 1;
+ src->eos_packets += 1;
// Test for EOF. Do this here to properly run into EOF even if other
// streams are disabled etc. If it somehow doesn't manage to reach the end
// after demuxing a high (bit arbitrary) number of packets, assume one of
// the streams went EOF early.
- bool eos_reached = p->eos_packets > 0;
- if (eos_reached && p->eos_packets < 100) {
- for (int n = 0; n < p->num_streams; n++) {
- struct virtual_stream *vs = p->streams[n];
+ bool eos_reached = src->eos_packets > 0;
+ if (eos_reached && src->eos_packets < 100) {
+ for (int n = 0; n < src->num_streams; n++) {
+ struct virtual_stream *vs = src->streams[n];
if (vs->selected) {
int max_packets = 0;
if (vs->sh->type == STREAM_AUDIO)
@@ -256,26 +314,30 @@ static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt
}
}
+ src->eof_reached = false;
+
if (eos_reached || !pkt) {
talloc_free(pkt);
struct segment *next = NULL;
- for (int n = 0; n < p->num_segments - 1; n++) {
- if (p->segments[n] == seg) {
- next = p->segments[n + 1];
+ for (int n = 0; n < src->num_segments - 1; n++) {
+ if (src->segments[n] == seg) {
+ next = src->segments[n + 1];
break;
}
}
- if (!next)
+ if (!next) {
+ src->eof_reached = true;
return false;
- switch_segment(demuxer, next, next->start, 0, true);
+ }
+ switch_segment(demuxer, src, next, next->start, 0, true);
return true; // reader will retry
}
if (pkt->stream < 0 || pkt->stream >= seg->num_stream_map)
goto drop;
- if (!p->dash) {
+ if (!src->dash) {
pkt->segmented = true;
if (!pkt->codec)
pkt->codec = demux_get_stream(seg->d, pkt->stream)->codec;
@@ -285,8 +347,8 @@ static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt
pkt->end = seg->end;
}
- int vs_index = seg->stream_map[pkt->stream];
- if (vs_index < 0)
+ struct virtual_stream *vs = seg->stream_map[pkt->stream];
+ if (!vs)
goto drop;
// for refresh seeks, demux.c prefers monotonically increasing packet pos
@@ -294,8 +356,6 @@ static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt
if (pkt->pos >= 0)
pkt->pos |= (seg->index & 0x7FFFULL) << 48;
- struct virtual_stream *vs = p->streams[vs_index];
-
if (pkt->pts != MP_NOPTS_VALUE && pkt->pts >= seg->end) {
// Trust the keyframe flag. Might not always be a good idea, but will
// be sufficient at least with mkv. The problem is that this flag is
@@ -308,6 +368,10 @@ static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt
}
}
+ double dts = pkt->dts != MP_NOPTS_VALUE ? pkt->dts : pkt->pts;
+ if (src->dts == MP_NOPTS_VALUE || (dts != MP_NOPTS_VALUE && dts > src->dts))
+ src->dts = dts;
+
pkt->stream = vs->sh->index;
*out_pkt = pkt;
return true;
@@ -322,24 +386,34 @@ static void print_timeline(struct demuxer *demuxer)
struct priv *p = demuxer->priv;
MP_VERBOSE(demuxer, "Timeline segments:\n");
- for (int n = 0; n < p->num_segments; n++) {
- struct segment *seg = p->segments[n];
- int src_num = n;
- for (int i = 0; i < n - 1; i++) {
- if (seg->d && p->segments[i]->d == seg->d) {
- src_num = i;
- break;
+ for (int x = 0; x < p->num_sources; x++) {
+ struct virtual_source *src = p->sources[x];
+
+ if (x >= 1)
+ MP_VERBOSE(demuxer, " --- new parallel stream ---\n");
+
+ for (int n = 0; n < src->num_segments; n++) {
+ struct segment *seg = src->segments[n];
+ int src_num = n;
+ for (int i = 0; i < n; i++) {
+ if (seg->d && src->segments[i]->d == seg->d) {
+ src_num = i;
+ break;
+ }
+ }
+ MP_VERBOSE(demuxer, " %2d: %12f [%12f] (", n, seg->start, seg->d_start);
+ for (int i = 0; i < seg->num_stream_map; i++) {
+ struct virtual_stream *vs = seg->stream_map[i];
+ MP_VERBOSE(demuxer, "%s%d", i ? " " : "",
+ vs ? vs->sh->index : -1);
}
+ MP_VERBOSE(demuxer, ") %d:'%s'\n", src_num, seg->url);
}
- MP_VERBOSE(demuxer, " %2d: %12f [%12f] (", n, seg->start, seg->d_start);
- for (int i = 0; i < seg->num_stream_map; i++)
- MP_VERBOSE(demuxer, "%s%d", i ? " " : "", seg->stream_map[i]);
- MP_VERBOSE(demuxer, ") %d:'%s'\n", src_num, seg->url);
+
+ if (src->dash)
+ MP_VERBOSE(demuxer, " (Using pseudo-DASH mode.)\n");
}
MP_VERBOSE(demuxer, "Total duration: %f\n", p->duration);
-
- if (p->dash)
- MP_VERBOSE(demuxer, "Durations and offsets are non-authoritative.\n");
}
static int d_open(struct demuxer *demuxer, enum demux_check check)
@@ -349,8 +423,6 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
if (!p->tl || p->tl->num_parts < 1)
return -1;
- p->duration = p->tl->parts[p->tl->num_parts].start;
-
demuxer->chapters = p->tl->chapters;
demuxer->num_chapters = p->tl->num_chapters;
@@ -361,8 +433,42 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
demuxer->editions = meta->editions;
demuxer->num_editions = meta->num_editions;
demuxer->edition = meta->edition;
+
+ for (struct timeline *tl = p->tl; tl; tl = tl->next)
+ add_tl(demuxer, tl);
+
demuxer->duration = p->duration;
+ print_timeline(demuxer);
+
+ demuxer->seekable = true;
+ demuxer->partially_seekable = false;
+
+ demuxer->filetype = talloc_asprintf(p, "edl/%s",
+ meta->filetype ? meta->filetype : meta->desc->name);
+
+ reselect_streams(demuxer);
+
+ return 0;
+}
+
+static void add_tl(struct demuxer *demuxer, struct timeline *tl)
+{
+ struct priv *p = demuxer->priv;
+
+ struct virtual_source *src = talloc_ptrtype(p, src);
+ *src = (struct virtual_source){
+ .tl = tl,
+ .dash = tl->dash,
+ .dts = MP_NOPTS_VALUE,
+ };
+
+ MP_TARRAY_APPEND(p, p->sources, p->num_sources, src);
+
+ p->duration = MPMAX(p->duration, tl->parts[tl->num_parts].start);
+
+ struct demuxer *meta = tl->track_layout;
+
int num_streams = demux_get_num_stream(meta);
for (int n = 0; n < num_streams; n++) {
struct sh_stream *sh = demux_get_stream(meta, n);
@@ -379,21 +485,26 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
demux_add_sh_stream(demuxer, new);
struct virtual_stream *vs = talloc_ptrtype(p, vs);
*vs = (struct virtual_stream){
+ .src = src,
.sh = new,
};
MP_TARRAY_APPEND(p, p->streams, p->num_streams, vs);
+ assert(demux_get_stream(demuxer, p->num_streams - 1) == new);
+ MP_TARRAY_APPEND(src, src->streams, src->num_streams, vs);
}
- for (int n = 0; n < p->tl->num_parts; n++) {
- struct timeline_part *part = &p->tl->parts[n];
- struct timeline_part *next = &p->tl->parts[n + 1];
+ for (int n = 0; n < tl->num_parts; n++) {
+ struct timeline_part *part = &tl->parts[n];
+ struct timeline_part *next = &tl->parts[n + 1];
// demux_timeline already does caching, doing it for the sub-demuxers
// would be pointless and wasteful.
- if (part->source)
+ if (part->source) {
demux_disable_cache(part->source);
+ demuxer->is_network |= part->source->is_network;
+ }
- struct segment *seg = talloc_ptrtype(p, seg);
+ struct segment *seg = talloc_ptrtype(src, seg);
*seg = (struct segment){
.d = part->source,
.url = part->source ? part->source->filename : part->url,
@@ -403,34 +514,27 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
.end = next->start,
};
- associate_streams(demuxer, seg);
+ associate_streams(demuxer, src, seg);
seg->index = n;
- MP_TARRAY_APPEND(p, p->segments, p->num_segments, seg);
+ MP_TARRAY_APPEND(src, src->segments, src->num_segments, seg);
}
- p->dash = p->tl->dash;
-
- print_timeline(demuxer);
-
- demuxer->seekable = true;
- demuxer->partially_seekable = false;
-
- demuxer->filetype = meta->filetype ? meta->filetype : meta->desc->name;
-
- demuxer->is_network = p->tl->demuxer->is_network;
-
- reselect_streams(demuxer);
-
- return 0;
+ demuxer->is_network |= tl->track_layout->is_network;
}
static void d_close(struct demuxer *demuxer)
{
struct priv *p = demuxer->priv;
+
+ for (int x = 0; x < p->num_sources; x++) {
+ struct virtual_source *src = p->sources[x];
+
+ src->current = NULL;
+ close_lazy_segments(demuxer, src);
+ }
+
struct demuxer *master = p->tl->demuxer;
- p->current = NULL;
- close_lazy_segments(demuxer);
timeline_destroy(p->tl);
demux_free(master);
}
diff --git a/demux/timeline.c b/demux/timeline.c
index c44f67b166..86e4921280 100644
--- a/demux/timeline.c
+++ b/demux/timeline.c
@@ -31,6 +31,8 @@ void timeline_destroy(struct timeline *tl)
{
if (!tl)
return;
+ // (Sub timeline elements may depend on allocations in the parent one.)
+ timeline_destroy(tl->next);
for (int n = 0; n < tl->num_sources; n++) {
struct demuxer *d = tl->sources[n];
if (d != tl->demuxer && d != tl->track_layout)
diff --git a/demux/timeline.h b/demux/timeline.h
index 21a6602ba8..69d93a4a2e 100644
--- a/demux/timeline.h
+++ b/demux/timeline.h
@@ -19,7 +19,7 @@ struct timeline {
bstr init_fragment;
bool dash;
- // All referenced files. The source file must be at sources[0].
+ // All referenced files.
struct demuxer **sources;
int num_sources;
@@ -33,6 +33,12 @@ struct timeline {
// Which source defines the overall track list (over the full timeline).
struct demuxer *track_layout;
+
+ // For tracks which require a separate opened demuxer, such as separate
+ // audio tracks. (For example, for ordered chapters this would be NULL,
+ // because all streams demux from the same file at a given time, while
+ // for DASH-style video+audio, each track would have its own timeline.)
+ struct timeline *next;
};
struct timeline *timeline_load(struct mpv_global *global, struct mp_log *log,