summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--audio/decode/dec_audio.c74
-rw-r--r--audio/decode/dec_audio.h2
-rw-r--r--demux/demux.c17
-rw-r--r--demux/demux.h1
-rw-r--r--demux/demux_timeline.c392
-rw-r--r--demux/packet.c5
-rw-r--r--demux/packet.h5
-rw-r--r--sub/dec_sub.c59
-rw-r--r--video/decode/dec_video.c71
-rw-r--r--video/decode/dec_video.h3
-rw-r--r--wscript_build.py1
11 files changed, 618 insertions, 12 deletions
diff --git a/audio/decode/dec_audio.c b/audio/decode/dec_audio.c
index 6d5210d179..c2272c5242 100644
--- a/audio/decode/dec_audio.c
+++ b/audio/decode/dec_audio.c
@@ -168,6 +168,9 @@ void audio_reset_decoding(struct dec_audio *d_audio)
d_audio->current_frame = NULL;
talloc_free(d_audio->packet);
d_audio->packet = NULL;
+ talloc_free(d_audio->new_segment);
+ d_audio->new_segment = NULL;
+ d_audio->start = d_audio->end = MP_NOPTS_VALUE;
}
static void fix_audio_pts(struct dec_audio *da)
@@ -192,6 +195,38 @@ static void fix_audio_pts(struct dec_audio *da)
da->pts += da->current_frame->samples / (double)da->current_frame->rate;
}
+static bool clip_frame(struct mp_audio *f, double start, double end)
+{
+ if (f->pts == MP_NOPTS_VALUE)
+ return false;
+ double f_end = f->pts + f->samples / (double)f->rate;
+ bool ended = false;
+ if (end != MP_NOPTS_VALUE) {
+ if (f_end >= end) {
+ if (f->pts >= end) {
+ f->samples = 0;
+ ended = true;
+ } else {
+ int new = (end - f->pts) * f->rate;
+ f->samples = MPCLAMP(new, 0, f->samples);
+ }
+ }
+ }
+ if (start != MP_NOPTS_VALUE) {
+ if (f->pts < start) {
+ if (f_end <= start) {
+ f->samples = 0;
+ } else {
+ int skip = (start - f->pts) * f->rate;
+ skip = MPCLAMP(skip, 0, f->samples);
+ mp_audio_skip_samples(f, skip);
+ f->pts += skip / (double)f->rate;
+ }
+ }
+ }
+ return ended;
+}
+
void audio_work(struct dec_audio *da)
{
if (da->current_frame)
@@ -202,7 +237,13 @@ void audio_work(struct dec_audio *da)
return;
}
- bool had_packet = !!da->packet;
+ if (da->packet && da->packet->new_segment) {
+ assert(!da->new_segment);
+ da->new_segment = da->packet;
+ da->packet = NULL;
+ }
+
+ bool had_packet = da->packet || da->new_segment;
int ret = da->ad_driver->decode_packet(da, da->packet, &da->current_frame);
if (ret < 0 || (da->packet && da->packet->len == 0)) {
@@ -223,6 +264,37 @@ void audio_work(struct dec_audio *da)
}
fix_audio_pts(da);
+
+ bool segment_end = true;
+
+ if (da->current_frame) {
+ segment_end = clip_frame(da->current_frame, da->start, da->end);
+ if (da->current_frame->samples == 0) {
+ talloc_free(da->current_frame);
+ da->current_frame = NULL;
+ }
+ }
+
+ // If there's a new segment, start it as soon as we're drained/finished.
+ if (segment_end && da->new_segment) {
+ struct demux_packet *new_segment = da->new_segment;
+ da->new_segment = NULL;
+
+ // Could avoid decoder reinit; would still need flush.
+ da->codec = new_segment->codec;
+ if (da->ad_driver)
+ da->ad_driver->uninit(da);
+ da->ad_driver = NULL;
+ audio_init_best_codec(da);
+
+ da->start = new_segment->start;
+ da->end = new_segment->end;
+
+ new_segment->new_segment = false;
+
+ da->packet = new_segment;
+ da->current_state = DATA_AGAIN;
+ }
}
// Fetch an audio frame decoded with audio_work(). Returns one of:
diff --git a/audio/decode/dec_audio.h b/audio/decode/dec_audio.h
index 88fc40aec9..7bc8b00b0f 100644
--- a/audio/decode/dec_audio.h
+++ b/audio/decode/dec_audio.h
@@ -43,7 +43,9 @@ struct dec_audio {
// Strictly internal (dec_audio.c).
double pts; // endpts of previous frame
+ double start, end;
struct demux_packet *packet;
+ struct demux_packet *new_segment;
struct mp_audio *current_frame;
int current_state;
};
diff --git a/demux/demux.c b/demux/demux.c
index 41b0f8f910..61becd7f04 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -36,6 +36,7 @@
#include "stream/stream.h"
#include "demux.h"
+#include "timeline.h"
#include "stheader.h"
#include "cue.h"
@@ -52,6 +53,7 @@ extern const demuxer_desc_t demuxer_desc_playlist;
extern const demuxer_desc_t demuxer_desc_disc;
extern const demuxer_desc_t demuxer_desc_rar;
extern const demuxer_desc_t demuxer_desc_libarchive;
+extern const demuxer_desc_t demuxer_desc_timeline;
/* Please do not add any new demuxers here. If you want to implement a new
* demuxer, add it to libavformat, except for wrappers around external
@@ -782,7 +784,7 @@ bool demux_has_packet(struct sh_stream *sh)
return has_packet;
}
-// Read and return any packet we find.
+// Read and return any packet we find. NULL means EOF.
struct demux_packet *demux_read_any_packet(struct demuxer *demuxer)
{
struct demux_internal *in = demuxer->in;
@@ -1082,7 +1084,8 @@ static struct demuxer *open_given_type(struct mpv_global *global,
mp_dbg(log, "Trying demuxer: %s (force-level: %s)\n",
desc->name, d_level(check));
- if (stream->seekable) // not for DVD/BD/DVB in particular
+ // not for DVD/BD/DVB in particular
+ if (stream->seekable && (!params || !params->timeline))
stream_seek(stream, 0);
// Peek this much data to avoid that stream_read() run by some demuxers
@@ -1110,6 +1113,16 @@ static struct demuxer *open_given_type(struct mpv_global *global,
demux_changed(in->d_thread, DEMUX_EVENT_ALL);
demux_update(demuxer);
stream_control(demuxer->stream, STREAM_CTRL_SET_READAHEAD, &(int){false});
+ struct timeline *tl = timeline_load(global, log, demuxer);
+ if (tl) {
+ struct demuxer_params params2 = {0};
+ params2.timeline = tl;
+ struct demuxer *sub = open_given_type(global, log,
+ &demuxer_desc_timeline, stream,
+ &params2, DEMUX_CHECK_FORCE);
+ if (sub)
+ return sub;
+ }
return demuxer;
}
diff --git a/demux/demux.h b/demux/demux.h
index 72ed15888b..05e645f728 100644
--- a/demux/demux.h
+++ b/demux/demux.h
@@ -160,6 +160,7 @@ struct demuxer_params {
struct matroska_segment_uid *matroska_wanted_uids;
int matroska_wanted_segment;
bool *matroska_was_valid;
+ struct timeline *timeline;
// -- demux_open_url() only
int stream_flags;
bool allow_capture;
diff --git a/demux/demux_timeline.c b/demux/demux_timeline.c
new file mode 100644
index 0000000000..9dbbff2158
--- /dev/null
+++ b/demux/demux_timeline.c
@@ -0,0 +1,392 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <limits.h>
+
+#include "common/common.h"
+#include "common/msg.h"
+
+#include "demux.h"
+#include "timeline.h"
+#include "stheader.h"
+
+struct segment {
+ int index;
+ double start, end;
+ double d_start;
+ 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
+ // 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;
+ int num_stream_map;
+};
+
+// Information for each stream on the virtual timeline. (Mirrors streams
+// exposed by demux_timeline.)
+struct virtual_stream {
+ struct sh_stream *sh; // stream exported by demux_timeline
+ bool selected; // ==demux_stream_is_selected(sh)
+ bool new_segment; // whether a new segment needs to be signaled
+ int eos_packets; // deal with b-frame delay
+};
+
+struct priv {
+ struct timeline *tl;
+
+ double duration;
+
+ 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;
+
+ double seek_pts;
+};
+
+static void switch_segment(struct demuxer *demuxer, struct segment *new,
+ double start_pts, int flags)
+{
+ struct priv *p = demuxer->priv;
+
+ if (p->current == new)
+ return;
+
+ if (!(flags & (SEEK_FORWARD | SEEK_BACKWARD)))
+ flags |= SEEK_BACKWARD;
+
+ MP_VERBOSE(demuxer, "switch to segment %d\n", new->index);
+
+ p->current = new;
+ demux_set_ts_offset(new->d, new->start - new->d_start);
+ demux_seek(new->d, start_pts, flags | SEEK_ABSOLUTE);
+
+ for (int n = 0; n < p->num_streams; n++) {
+ struct virtual_stream *vs = &p->streams[n];
+ vs->new_segment = true;
+ vs->eos_packets = 0;
+ }
+
+ p->eos_packets = 0;
+}
+
+static void d_seek(struct demuxer *demuxer, double rel_seek_secs, int flags)
+{
+ struct priv *p = demuxer->priv;
+
+ double pts = p->seek_pts;
+ if (flags & SEEK_ABSOLUTE)
+ pts = 0.0f;
+
+ if (flags & SEEK_FACTOR) {
+ pts += p->duration * rel_seek_secs;
+ } else {
+ pts += rel_seek_secs;
+ }
+
+ flags &= SEEK_FORWARD | SEEK_BACKWARD | 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;
+ }
+ }
+
+ p->current = NULL; // force seek
+ switch_segment(demuxer, new, pts, flags);
+
+ p->seek_pts = pts;
+}
+
+static int d_fill_buffer(struct demuxer *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ if (!p->current)
+ switch_segment(demuxer, p->segments[0], 0, 0);
+
+ struct segment *seg = p->current;
+
+ struct demux_packet *pkt = demux_read_any_packet(seg->d);
+ if (!pkt || pkt->pts >= seg->end)
+ p->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];
+ if (vs->selected) {
+ int max_packets = 0;
+ if (vs->sh->type == STREAM_AUDIO)
+ max_packets = 1;
+ if (vs->sh->type == STREAM_VIDEO)
+ max_packets = 16;
+ eos_reached &= vs->eos_packets >= max_packets;
+ }
+ }
+ }
+
+ 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];
+ break;
+ }
+ }
+ if (!next)
+ return 0;
+ switch_segment(demuxer, next, next->start, 0);
+ return 1; // reader will retry
+ }
+
+ if (pkt->stream < 0 || pkt->stream > seg->num_stream_map)
+ goto drop;
+
+ if (!pkt->codec)
+ pkt->codec = demux_get_stream(seg->d, pkt->stream)->codec;
+
+ if (pkt->start == MP_NOPTS_VALUE || pkt->start < seg->start)
+ pkt->start = seg->start;
+ if (pkt->end == MP_NOPTS_VALUE || pkt->end > seg->end)
+ pkt->end = seg->end;
+
+ pkt->stream = seg->stream_map[pkt->stream];
+ if (pkt->stream < 0)
+ goto drop;
+
+ struct virtual_stream *vs = &p->streams[pkt->stream];
+
+ 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
+ // not well-defined in libavformat and is container-dependent.
+ if (pkt->keyframe || vs->eos_packets == INT_MAX) {
+ vs->eos_packets = INT_MAX;
+ goto drop;
+ } else {
+ vs->eos_packets += 1;
+ }
+ }
+
+ pkt->new_segment = vs->new_segment;
+ vs->new_segment = false;
+
+ demux_add_packet(vs->sh, pkt);
+ return 1;
+
+drop:
+ talloc_free(pkt);
+ return 1;
+}
+
+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 = -1;
+ for (int i = 0; i < p->tl->num_sources; i++) {
+ if (p->tl->sources[i] == 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++)
+ MP_VERBOSE(demuxer, "%s%d", i ? " " : "", seg->stream_map[i]);
+ MP_VERBOSE(demuxer, ") %d:'%s'\n", src_num, seg->d->filename);
+ }
+ MP_VERBOSE(demuxer, "Total duration: %f\n", p->duration);
+}
+
+static bool target_stream_used(struct segment *seg, int target_index)
+{
+ for (int n = 0; n < seg->num_stream_map; n++) {
+ if (seg->stream_map[n] == target_index)
+ return true;
+ }
+ return false;
+}
+
+// Create mapping from segment streams to virtual timeline streams.
+static void associate_streams(struct demuxer *demuxer, struct segment *seg)
+{
+ struct priv *p = demuxer->priv;
+
+ 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;
+ }
+ }
+ }
+
+ MP_TARRAY_APPEND(seg, seg->stream_map, seg->num_stream_map,
+ other ? other->index : -1);
+
+ counts[sh->type] += 1;
+ }
+}
+
+static int d_open(struct demuxer *demuxer, enum demux_check check)
+{
+ struct priv *p = demuxer->priv = talloc_zero(demuxer, struct priv);
+ p->tl = demuxer->params ? demuxer->params->timeline : NULL;
+ 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;
+
+ struct demuxer *meta = p->tl->track_layout;
+ demuxer->metadata = meta->metadata;
+ demuxer->attachments = meta->attachments;
+ demuxer->num_attachments = meta->num_attachments;
+
+ 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);
+ struct sh_stream *new = demux_alloc_sh_stream(sh->type);
+ new->demuxer_id = sh->demuxer_id;
+ new->codec = sh->codec;
+ new->title = sh->title;
+ new->lang = sh->lang;
+ new->default_track = sh->default_track;
+ new->forced_track = sh->forced_track;
+ new->hls_bitrate = sh->hls_bitrate;
+ new->missing_timestamps = sh->missing_timestamps;
+ demux_add_sh_stream(demuxer, new);
+ struct virtual_stream vs = {
+ .sh = new,
+ };
+ MP_TARRAY_APPEND(p, p->streams, p->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];
+
+ struct segment *seg = talloc_ptrtype(p, seg);
+ *seg = (struct segment){
+ .d = part->source,
+ .d_start = part->source_start,
+ .start = part->start,
+ .end = next->start,
+ };
+
+ associate_streams(demuxer, seg);
+
+ seg->index = n;
+ MP_TARRAY_APPEND(p, p->segments, p->num_segments, seg);
+ }
+
+ print_timeline(demuxer);
+
+ demuxer->seekable = true;
+ demuxer->partially_seekable = true;
+
+ return 0;
+}
+
+static void d_close(struct demuxer *demuxer)
+{
+ struct priv *p = demuxer->priv;
+ struct demuxer *master = p->tl->demuxer;
+ timeline_destroy(p->tl);
+ free_demuxer(master);
+}
+
+static void reselect_streams(struct demuxer *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ for (int n = 0; n < p->num_streams; n++) {
+ struct virtual_stream *vs = &p->streams[n];
+ 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++) {
+ 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;
+ demuxer_select_track(seg->d, sh, selected);
+ }
+ }
+}
+
+static int d_control(struct demuxer *demuxer, int cmd, void *arg)
+{
+ struct priv *p = demuxer->priv;
+
+ switch (cmd) {
+ case DEMUXER_CTRL_GET_TIME_LENGTH: {
+ *(double *)arg = p->duration;
+ return DEMUXER_CTRL_OK;
+ }
+ case DEMUXER_CTRL_SWITCHED_TRACKS:
+ reselect_streams(demuxer);
+ return DEMUXER_CTRL_OK;
+ }
+ return DEMUXER_CTRL_NOTIMPL;
+}
+
+const demuxer_desc_t demuxer_desc_timeline = {
+ .name = "timeline",
+ .desc = "timeline segment merging wrapper",
+ .fill_buffer = d_fill_buffer,
+ .open = d_open,
+ .close = d_close,
+ .seek = d_seek,
+ .control = d_control,
+};
diff --git a/demux/packet.c b/demux/packet.c
index 22b111b0ce..32fabc4f78 100644
--- a/demux/packet.c
+++ b/demux/packet.c
@@ -49,6 +49,8 @@ struct demux_packet *new_demux_packet_from_avpacket(struct AVPacket *avpkt)
.dts = MP_NOPTS_VALUE,
.duration = -1,
.pos = -1,
+ .start = MP_NOPTS_VALUE,
+ .end = MP_NOPTS_VALUE,
.stream = -1,
.avpacket = talloc_zero(dp, AVPacket),
};
@@ -106,6 +108,9 @@ void demux_packet_copy_attribs(struct demux_packet *dst, struct demux_packet *sr
dst->dts = src->dts;
dst->duration = src->duration;
dst->pos = src->pos;
+ dst->start = src->start;
+ dst->end = src->end;
+ dst->new_segment = src->new_segment;
dst->keyframe = src->keyframe;
dst->stream = src->stream;
}
diff --git a/demux/packet.h b/demux/packet.h
index 82dad6f90f..723dbc7e54 100644
--- a/demux/packet.h
+++ b/demux/packet.h
@@ -35,6 +35,11 @@ typedef struct demux_packet {
int64_t pos; // position in source file byte stream
int stream; // source stream index
+ // segmentation (ordered chapters, EDL)
+ struct mp_codec_params *codec;
+ double start, end;
+ bool new_segment;
+
// private
struct demux_packet *next;
struct AVPacket *avpacket; // keep the buffer allocation and sidedata
diff --git a/sub/dec_sub.c b/sub/dec_sub.c
index 5a952e4fd5..58562140fa 100644
--- a/sub/dec_sub.c
+++ b/sub/dec_sub.c
@@ -50,12 +50,17 @@ struct dec_sub {
struct MPOpts *opts;
struct demuxer *demuxer;
- struct mp_codec_params *codec;
struct sh_stream *sh;
double last_pkt_pts;
+ struct mp_codec_params *codec;
+ double start, end;
+
+ double last_vo_pts;
struct sd *sd;
+
+ struct demux_packet *new_segment;
};
void sub_lock(struct dec_sub *sub)
@@ -121,6 +126,9 @@ struct dec_sub *sub_create(struct mpv_global *global, struct demuxer *demuxer,
.codec = sh->codec,
.demuxer = demuxer,
.last_pkt_pts = MP_NOPTS_VALUE,
+ .last_vo_pts = MP_NOPTS_VALUE,
+ .start = MP_NOPTS_VALUE,
+ .end = MP_NOPTS_VALUE,
};
mpthread_mutex_init_recursive(&sub->lock);
@@ -132,6 +140,31 @@ struct dec_sub *sub_create(struct mpv_global *global, struct demuxer *demuxer,
return NULL;
}
+// Called locked.
+static void update_segment(struct dec_sub *sub)
+{
+ if (sub->new_segment && sub->last_vo_pts != MP_NOPTS_VALUE &&
+ sub->last_vo_pts >= sub->new_segment->start)
+ {
+ sub->codec = sub->new_segment->codec;
+ sub->start = sub->new_segment->start;
+ sub->end = sub->new_segment->end;
+ struct sd *new = init_decoder(sub);
+ if (new) {
+ sub->sd->driver->uninit(sub->sd);
+ talloc_free(sub->sd);
+ sub->sd = new;
+ } else {
+ // We'll just keep the current decoder, and feed it possibly
+ // invalid data (not our fault if it crashes or something).
+ MP_ERR(sub, "Can't change to new codec.\n");
+ }
+ sub->sd->driver->decode(sub->sd, sub->new_segment);
+ talloc_free(sub->new_segment);
+ sub->new_segment = NULL;
+ }
+}
+
// Read all packets from the demuxer and decode/add them. Returns false if
// there are circumstances which makes this not possible.
bool sub_read_all_packets(struct dec_sub *sub)
@@ -170,6 +203,9 @@ bool sub_read_packets(struct dec_sub *sub, double video_pts)
if (!read_more)
break;
+ if (sub->new_segment)
+ break;
+
struct demux_packet *pkt;
int st = demux_read_packet_async(sub->sh, &pkt);
// Note: "wait" (st==0) happens with non-interleaved streams only, and
@@ -183,8 +219,16 @@ bool sub_read_packets(struct dec_sub *sub, double video_pts)
break;
}
- sub->sd->driver->decode(sub->sd, pkt);
sub->last_pkt_pts = pkt->pts;
+
+ if (pkt->new_segment) {
+ sub->new_segment = pkt;
+ // Note that this can be delayed to a much later point in time.
+ update_segment(sub);
+ break;
+ }
+
+ sub->sd->driver->decode(sub->sd, pkt);
talloc_free(pkt);
}
pthread_mutex_unlock(&sub->lock);
@@ -199,6 +243,9 @@ void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, double pts,
{
struct MPOpts *opts = sub->opts;
+ sub->last_vo_pts = pts;
+ update_segment(sub);
+
*res = (struct sub_bitmaps) {0};
if (opts->sub_visibility && sub->sd->driver->get_bitmaps)
sub->sd->driver->get_bitmaps(sub->sd, dim, pts, res);
@@ -212,6 +259,10 @@ char *sub_get_text(struct dec_sub *sub, double pts)
pthread_mutex_lock(&sub->lock);
struct MPOpts *opts = sub->opts;
char *text = NULL;
+
+ sub->last_vo_pts = pts;
+ update_segment(sub);
+
if (opts->sub_visibility && sub->sd->driver->get_text)
text = sub->sd->driver->get_text(sub->sd, pts);
pthread_mutex_unlock(&sub->lock);
@@ -224,6 +275,10 @@ void sub_reset(struct dec_sub *sub)
if (sub->sd->driver->reset)
sub->sd->driver->reset(sub->sd);
sub->last_pkt_pts = MP_NOPTS_VALUE;
+ sub->start = sub->end = MP_NOPTS_VALUE;
+ sub->last_vo_pts = MP_NOPTS_VALUE;
+ talloc_free(sub->new_segment);
+ sub->new_segment = NULL;
pthread_mutex_unlock(&sub->lock);
}
diff --git a/video/decode/dec_video.c b/video/decode/dec_video.c
index 0487ac383f..0a33adfc28 100644
--- a/video/decode/dec_video.c
+++ b/video/decode/dec_video.c
@@ -68,6 +68,11 @@ void video_reset(struct dec_video *d_video)
d_video->dropped_frames = 0;
d_video->current_state = DATA_AGAIN;
mp_image_unrefp(&d_video->current_mpi);
+ talloc_free(d_video->packet);
+ d_video->packet = NULL;
+ talloc_free(d_video->new_segment);
+ d_video->new_segment = NULL;
+ d_video->start = d_video->end = MP_NOPTS_VALUE;
}
int video_vd_control(struct dec_video *d_video, int cmd, void *arg)
@@ -88,6 +93,8 @@ void video_uninit(struct dec_video *d_video)
MP_VERBOSE(d_video, "Uninit video.\n");
d_video->vd_driver->uninit(d_video);
}
+ talloc_free(d_video->packet);
+ talloc_free(d_video->new_segment);
talloc_free(d_video);
}
@@ -243,6 +250,9 @@ static struct mp_image *decode_packet(struct dec_video *d_video,
struct MPOpts *opts = d_video->opts;
bool avi_pts = d_video->codec->avi_dts && opts->correct_pts;
+ if (!d_video->vd_driver)
+ return NULL;
+
struct demux_packet packet_copy;
if (packet && packet->dts == MP_NOPTS_VALUE && !avi_pts) {
packet_copy = *packet;
@@ -371,22 +381,37 @@ void video_work(struct dec_video *d_video)
return;
}
- struct demux_packet *pkt;
- if (demux_read_packet_async(d_video->header, &pkt) == 0) {
+ if (!d_video->packet && !d_video->new_segment &&
+ demux_read_packet_async(d_video->header, &d_video->packet) == 0)
+ {
d_video->current_state = DATA_WAIT;
return;
}
+ if (d_video->packet && d_video->packet->new_segment) {
+ assert(!d_video->new_segment);
+ d_video->new_segment = d_video->packet;
+ d_video->packet = NULL;
+ }
+
+ bool had_input_packet = !!d_video->packet;
+ bool had_packet = had_input_packet || d_video->new_segment;
+
+ double start_pts = d_video->start_pts;
+ if (d_video->start != MP_NOPTS_VALUE && (start_pts == MP_NOPTS_VALUE ||
+ d_video->start > start_pts))
+ start_pts = d_video->start;
+
int framedrop_type = d_video->framedrop_enabled ? 1 : 0;
- if (d_video->start_pts != MP_NOPTS_VALUE && pkt &&
- pkt->pts < d_video->start_pts - .005 &&
+ if (start_pts != MP_NOPTS_VALUE && d_video->packet &&
+ d_video->packet->pts < start_pts - .005 &&
!d_video->has_broken_packet_pts)
{
framedrop_type = 2;
}
- d_video->current_mpi = decode_packet(d_video, pkt, framedrop_type);
- bool had_packet = !!pkt;
- talloc_free(pkt);
+ d_video->current_mpi = decode_packet(d_video, d_video->packet, framedrop_type);
+ talloc_free(d_video->packet); // always fully consumed
+ d_video->packet = NULL;
d_video->current_state = DATA_OK;
if (!d_video->current_mpi) {
@@ -397,6 +422,38 @@ void video_work(struct dec_video *d_video)
d_video->current_state = DATA_AGAIN;
}
}
+
+ bool segment_ended = !d_video->current_mpi && !had_input_packet;
+
+ if (d_video->current_mpi && d_video->current_mpi->pts != MP_NOPTS_VALUE) {
+ double vpts = d_video->current_mpi->pts;
+ segment_ended = d_video->end != MP_NOPTS_VALUE && vpts >= d_video->end;
+ if ((start_pts != MP_NOPTS_VALUE && vpts < start_pts) || segment_ended) {
+ talloc_free(d_video->current_mpi);
+ d_video->current_mpi = NULL;
+ }
+ }
+
+ // If there's a new segment, start it as soon as we're drained/finished.
+ if (segment_ended && d_video->new_segment) {
+ struct demux_packet *new_segment = d_video->new_segment;
+ d_video->new_segment = NULL;
+
+ // Could avoid decoder reinit; would still need flush.
+ d_video->codec = new_segment->codec;
+ if (d_video->vd_driver)
+ d_video->vd_driver->uninit(d_video);
+ d_video->vd_driver = NULL;
+ video_init_best_codec(d_video);
+
+ d_video->start = new_segment->start;
+ d_video->end = new_segment->end;
+
+ new_segment->new_segment = false;
+
+ d_video->packet = new_segment;
+ d_video->current_state = DATA_AGAIN;
+ }
}
// Fetch an image decoded with video_work(). Returns one of:
diff --git a/video/decode/dec_video.h b/video/decode/dec_video.h
index d4c12a7bc4..f4646a97d0 100644
--- a/video/decode/dec_video.h
+++ b/video/decode/dec_video.h
@@ -69,6 +69,9 @@ struct dec_video {
float initial_decoder_aspect;
double start_pts;
+ double start, end;
+ struct demux_packet *new_segment;
+ struct demux_packet *packet;
bool framedrop_enabled;
struct mp_image *cover_art_mpi;
struct mp_image *current_mpi;
diff --git a/wscript_build.py b/wscript_build.py
index 75dc763f35..d759a6132b 100644
--- a/wscript_build.py
+++ b/wscript_build.py
@@ -177,6 +177,7 @@ def build(ctx):
( "demux/demux_playlist.c" ),
( "demux/demux_raw.c" ),
( "demux/demux_rar.c" ),
+ ( "demux/demux_timeline.c" ),
( "demux/demux_tv.c", "tv" ),
( "demux/ebml.c" ),
( "demux/packet.c" ),