diff options
-rw-r--r-- | audio/decode/dec_audio.c | 74 | ||||
-rw-r--r-- | audio/decode/dec_audio.h | 2 | ||||
-rw-r--r-- | demux/demux.c | 17 | ||||
-rw-r--r-- | demux/demux.h | 1 | ||||
-rw-r--r-- | demux/demux_timeline.c | 392 | ||||
-rw-r--r-- | demux/packet.c | 5 | ||||
-rw-r--r-- | demux/packet.h | 5 | ||||
-rw-r--r-- | sub/dec_sub.c | 59 | ||||
-rw-r--r-- | video/decode/dec_video.c | 71 | ||||
-rw-r--r-- | video/decode/dec_video.h | 3 | ||||
-rw-r--r-- | wscript_build.py | 1 |
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, + ¶ms2, 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" ), |