diff options
-rw-r--r-- | DOCS/interface-changes.rst | 1 | ||||
-rw-r--r-- | DOCS/man/options.rst | 30 | ||||
-rw-r--r-- | demux/demux.c | 488 | ||||
-rw-r--r-- | demux/demux.h | 8 | ||||
-rw-r--r-- | player/command.c | 5 |
5 files changed, 375 insertions, 157 deletions
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst index 608248f88f..7ccf66758c 100644 --- a/DOCS/interface-changes.rst +++ b/DOCS/interface-changes.rst @@ -45,6 +45,7 @@ Interface changes audio output drivers for quite a while (coreaudio used to provide it) - deprecate --videotoolbox-format (use --hwdec-image-format, which affects most other hwaccels) + - remove deprecated --demuxer-max-packets --- mpv 0.27.0 --- - drop previously deprecated --field-dominance option - drop previously deprecated "osd" command diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 8ff7ccd508..d6e7cf56cf 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -2839,11 +2839,31 @@ Demuxer See ``--list-options`` for defaults and value range. -``--demuxer-max-packets=<packets>`` - Quite similar ``--demuxer-max-bytes=<bytes>``. Deprecated, because the - other option does basically the same job. Since mpv 0.25.0, the code - tries to account for per-packet overhead, which is why this option becomes - rather pointless. +``--demuxer-max-back-bytes=<value>`` + This controls how much past data the demuxer is allowed to preserve. This + is useful only if the ``--demuxer-seekable-cache`` option is enabled. + Unlike the forward cache, there is no control how many seconds are actually + cached - it will simply use as much memory this option allows. Setting this + option to 0 will strictly disable any back buffer. + + Keep in mind that other buffers in the player (like decoders) will cause the + demuxer to cache "future" frames in the back buffer, which can skew the + impression about how much data the backbuffer contains. + + See ``--list-options`` for defaults and value range. + +``--demuxer-seekable-cache=<yes|no>`` + This controls whether seeking can use the demuxer cache (default: no). If + enabled, short seek offsets will not trigger a low level demuxer seek + (which means for example that slow network round trips or FFmpeg seek bugs + can be avoided). If a seek cannot happen within the cached range, a low + level seek will be triggered. Seeking outside of the cache will always + discard the full cache. + + Keep in mind that some events can flush the cache or force a low level + seek anyway, such as switching tracks, or attmepting to seek before the + start or after the end of the file. This option is experimental - thus + disabled, and bugs are to be expected. ``--demuxer-thread=<yes|no>`` Run the demuxer in a separate thread, and let it prefetch a certain amount diff --git a/demux/demux.c b/demux/demux.c index fecccbabc0..aa68ce7e13 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -84,12 +84,13 @@ const demuxer_desc_t *const demuxer_list[] = { }; struct demux_opts { - int max_packs; int max_bytes; + int max_bytes_bw; double min_secs; int force_seekable; double min_secs_cache; int access_references; + int seekable_cache; }; #define OPT_BASE_STRUCT struct demux_opts @@ -97,18 +98,18 @@ struct demux_opts { const struct m_sub_options demux_conf = { .opts = (const struct m_option[]){ OPT_DOUBLE("demuxer-readahead-secs", min_secs, M_OPT_MIN, .min = 0), - OPT_INTRANGE("demuxer-max-packets", max_packs, 0, 0, INT_MAX, - .deprecation_message = "use --demuxer-max-bytes"), OPT_INTRANGE("demuxer-max-bytes", max_bytes, 0, 0, INT_MAX), + OPT_INTRANGE("demuxer-max-back-bytes", max_bytes_bw, 0, 0, INT_MAX), OPT_FLAG("force-seekable", force_seekable, 0), OPT_DOUBLE("cache-secs", min_secs_cache, M_OPT_MIN, .min = 0), OPT_FLAG("access-references", access_references, 0), + OPT_FLAG("demuxer-seekable-cache", seekable_cache, 0), {0} }, .size = sizeof(struct demux_opts), .defaults = &(const struct demux_opts){ - .max_packs = INT_MAX, .max_bytes = 400 * 1024 * 1024, + .max_bytes_bw = 0, .min_secs = 1.0, .min_secs_cache = 10.0, .access_references = 1, @@ -150,8 +151,9 @@ struct demux_internal { bool idle; bool autoselect; double min_secs; - int max_packs; int max_bytes; + int max_bytes_bw; + int seekable_cache; // Set if we know that we are at the start of the file. This is used to // avoid a redundant initial seek after enabling streams. We could just @@ -183,7 +185,9 @@ struct demux_internal { struct demux_stream { struct demux_internal *in; enum stream_type type; - // all fields are protected by in->lock + // --- all fields are protected by in->lock + + // demuxer state bool selected; // user wants packets from this stream bool active; // try to keep at least 1 packet queued // if false, this stream is disabled, or passively @@ -193,25 +197,30 @@ struct demux_stream { bool refreshing; bool correct_dts; // packet DTS is strictly monotonically increasing bool correct_pos; // packet pos is strictly monotonically increasing - size_t packs; // number of packets in buffer - size_t bytes; // total bytes of packets in buffer - double base_ts; // timestamp of the last packet returned to decoder + size_t fw_packs; // number of packets in buffer (forward) + size_t fw_bytes; // total bytes of packets in buffer (forward) + size_t bw_bytes; // same as fw_bytes, but for back buffer + int64_t last_pos; + double last_dts; double last_ts; // timestamp of the last packet added to queue + double back_pts; // smallest timestamp on the start of the back buffer + struct demux_packet *queue_head; // start of the full queue + struct demux_packet *queue_tail; // end of the full queue + + // reader (decoder) state (bitrate calculations are part of it because we + // want to return the bitrate closest to the "current position") + double base_ts; // timestamp of the last packet returned to decoder double last_br_ts; // timestamp of last packet bitrate was calculated size_t last_br_bytes; // summed packet sizes since last bitrate calculation double bitrate; - int64_t last_pos; - double last_dts; - struct demux_packet *head; - struct demux_packet *tail; - - struct demux_packet *attached_picture; + struct demux_packet *reader_head; // points at current decoder position bool attached_picture_added; - bool ignore_eof; // ignore stream in underrun detection + struct demux_packet *attached_picture; // for closed captions (demuxer_feed_caption) struct sh_stream *cc; + bool ignore_eof; // ignore stream in underrun detection }; // Return "a", or if that is NOPTS, return "def". @@ -226,30 +235,40 @@ static void demuxer_sort_chapters(demuxer_t *demuxer); static void *demux_thread(void *pctx); static void update_cache(struct demux_internal *in); static int cached_demux_control(struct demux_internal *in, int cmd, void *arg); +static void clear_demux_state(struct demux_internal *in); -// called locked -static void ds_flush(struct demux_stream *ds) +static void ds_clear_reader_state(struct demux_stream *ds) { - demux_packet_t *dp = ds->head; + ds->reader_head = NULL; + ds->base_ts = ds->last_br_ts = MP_NOPTS_VALUE; + ds->last_br_bytes = 0; + ds->bitrate = -1; + ds->attached_picture_added = false; +} + +static void ds_clear_demux_state(struct demux_stream *ds) +{ + ds_clear_reader_state(ds); + + demux_packet_t *dp = ds->queue_head; while (dp) { demux_packet_t *dn = dp->next; free_demux_packet(dp); dp = dn; } - ds->head = ds->tail = NULL; - ds->packs = 0; - ds->bytes = 0; - ds->last_ts = ds->base_ts = ds->last_br_ts = MP_NOPTS_VALUE; - ds->last_br_bytes = 0; - ds->bitrate = -1; + ds->queue_head = ds->queue_tail = NULL; + + ds->fw_packs = 0; + ds->fw_bytes = 0; + ds->bw_bytes = 0; ds->eof = false; ds->active = false; ds->refreshing = false; ds->need_refresh = false; - ds->last_pos = -1; - ds->last_dts = MP_NOPTS_VALUE; ds->correct_dts = ds->correct_pos = true; - ds->attached_picture_added = false; + ds->last_pos = -1; + ds->last_ts = ds->last_dts = MP_NOPTS_VALUE; + ds->back_pts = MP_NOPTS_VALUE; } void demux_set_ts_offset(struct demuxer *demuxer, double offset) @@ -381,10 +400,11 @@ void free_demuxer(demuxer_t *demuxer) if (demuxer->desc->close) demuxer->desc->close(in->d_thread); - for (int n = in->num_streams - 1; n >= 0; n--) { - ds_flush(in->streams[n]->ds); + + clear_demux_state(in); + + for (int n = in->num_streams - 1; n >= 0; n--) talloc_free(in->streams[n]); - } pthread_mutex_destroy(&in->lock); pthread_cond_destroy(&in->wakeup); talloc_free(demuxer); @@ -530,6 +550,29 @@ static double get_refresh_seek_pts(struct demux_internal *in) return start_ts - 1.0; } +// Get the PTS in the keyframe range starting at or following dp. We assume +// that the minimum PTS values within a keyframe range are strictly monotonic +// increasing relative to the range after it. Since we don't assume that the +// first packet has the minimum PTS, a search within the keyframe range is done. +// This function does not assume dp->keyframe==true, because it deals with weird +// cases like apparently seeking to non-keyframes, or pruning the complete +// backbuffer, which might end up with non-keyframes even at queue start. +static double recompute_keyframe_target_pts(struct demux_packet *dp) +{ + int keyframes = 0; + double res = MP_NOPTS_VALUE; + while (dp) { + if (dp->keyframe) + keyframes++; + if (keyframes == 2) + break; + if (keyframes == 1) + res = MP_PTS_MIN(res, dp->pts); + dp = dp->next; + } + return res; +} + void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp) { struct demux_stream *ds = stream ? stream->ds : NULL; @@ -568,16 +611,24 @@ void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp) dp->stream = stream->index; dp->next = NULL; - ds->packs++; - ds->bytes += demux_packet_estimate_total_size(dp); - if (ds->tail) { + ds->fw_packs++; + ds->fw_bytes += demux_packet_estimate_total_size(dp); + if (ds->queue_tail) { // next packet in stream - ds->tail->next = dp; - ds->tail = dp; + ds->queue_tail->next = dp; + ds->queue_tail = dp; } else { // first packet in stream - ds->head = ds->tail = dp; + ds->queue_head = ds->queue_tail = dp; } + // (keep in mind that even if the reader went out of data, the queue is not + // necessarily empty due to the backbuffer) + if (!ds->reader_head) + ds->reader_head = dp; + + // (In theory it'd be more efficient to make this incremental.) + if (ds->back_pts == MP_NOPTS_VALUE && dp->keyframe) + ds->back_pts = recompute_keyframe_target_pts(ds->queue_head); if (!ds->ignore_eof) { // obviously not true anymore @@ -598,9 +649,10 @@ void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp) MP_DBG(in, "append packet to %s: size=%d pts=%f dts=%f pos=%"PRIi64" " "[num=%zd size=%zd]\n", stream_type_name(stream->type), - dp->len, dp->pts, dp->dts, dp->pos, ds->packs, ds->bytes); + dp->len, dp->pts, dp->dts, dp->pos, ds->fw_packs, ds->fw_bytes); - if (ds->in->wakeup_cb && !ds->head->next) + // Wake up if this was the first packet after start/possible underrun. + if (ds->in->wakeup_cb && !ds->reader_head->next) ds->in->wakeup_cb(ds->in->wakeup_cb_ctx); pthread_cond_signal(&in->wakeup); pthread_mutex_unlock(&in->lock); @@ -616,20 +668,19 @@ static bool read_packet(struct demux_internal *in) // the minimum, or if a stream explicitly needs new packets. Also includes // safe-guards against packet queue overflow. bool active = false, read_more = false; - size_t packs = 0, bytes = 0; + size_t bytes = 0; for (int n = 0; n < in->num_streams; n++) { struct demux_stream *ds = in->streams[n]->ds; active |= ds->active; - read_more |= (ds->active && !ds->head) || ds->refreshing; - packs += ds->packs; - bytes += ds->bytes; + read_more |= (ds->active && !ds->reader_head) || ds->refreshing; + bytes += ds->fw_bytes; if (ds->active && ds->last_ts != MP_NOPTS_VALUE && in->min_secs > 0 && ds->last_ts >= ds->base_ts) read_more |= ds->last_ts - ds->base_ts < in->min_secs; } - MP_DBG(in, "packets=%zd, bytes=%zd, active=%d, more=%d\n", - packs, bytes, active, read_more); - if (packs >= in->max_packs || bytes >= in->max_bytes) { + MP_DBG(in, "bytes=%zd, active=%d, more=%d\n", + bytes, active, read_more); + if (bytes >= in->max_bytes) { if (!in->warned_queue_overflow) { in->warned_queue_overflow = true; MP_WARN(in, "Too many packets in the demuxer packet queues:\n"); @@ -637,13 +688,14 @@ static bool read_packet(struct demux_internal *in) struct demux_stream *ds = in->streams[n]->ds; if (ds->selected) { MP_WARN(in, " %s/%d: %zd packets, %zd bytes\n", - stream_type_name(ds->type), n, ds->packs, ds->bytes); + stream_type_name(ds->type), n, + ds->fw_packs, ds->fw_bytes); } } } for (int n = 0; n < in->num_streams; n++) { struct demux_stream *ds = in->streams[n]->ds; - bool eof = !ds->head; + bool eof = !ds->reader_head; if (eof && !ds->eof) { if (in->wakeup_cb) in->wakeup_cb(in->wakeup_cb_ctx); @@ -698,6 +750,69 @@ static bool read_packet(struct demux_internal *in) return true; } +static void prune_old_packets(struct demux_internal *in) +{ + size_t buffered = 0; + for (int n = 0; n < in->num_streams; n++) + buffered += in->streams[n]->ds->bw_bytes; + + MP_TRACE(in, "total backbuffer = %zd\n", buffered); + + // It's not clear what the ideal way to prune old packets is. For now, we + // prune the oldest packet runs, as long as the total cache amount is too + // big. + while (buffered > in->max_bytes_bw) { + double earliest_ts = MP_NOPTS_VALUE; + int earliest_stream = -1; + + for (int n = 0; n < in->num_streams; n++) { + struct demux_stream *ds = in->streams[n]->ds; + + if (ds->queue_head && ds->queue_head != ds->reader_head) { + struct demux_packet *dp = ds->queue_head; + double ts = PTS_OR_DEF(dp->dts, dp->pts); + // Note: in obscure cases, packets might have no timestamps set, + // in which case we still need to prune _something_. + if (earliest_ts == MP_NOPTS_VALUE || + (ts != MP_NOPTS_VALUE && ts < earliest_ts)) + { + earliest_ts = ts; + earliest_stream = n; + } + } + } + + assert(earliest_stream >= 0); // incorrect accounting of "buffered"? + struct demux_stream *ds = in->streams[earliest_stream]->ds; + + // Prune all packets until the next keyframe or reader_head. Keeping + // those packets would not help with seeking at all, so we strictly + // drop them. + // Note: might be pretty inefficient for streams with many small audio + // or subtitle packets. (All are keyframe and selection logic runs for + // every packet.) + bool dropped_one = false; + while (ds->queue_head && ds->queue_head != ds->reader_head) { + struct demux_packet *dp = ds->queue_head; + if (dp->keyframe && dropped_one) + break; + dropped_one = true; + size_t bytes = demux_packet_estimate_total_size(dp); + buffered -= bytes; + MP_TRACE(in, "dropping backbuffer packet size %zd from stream %d\n", + bytes, earliest_stream); + + ds->queue_head = dp->next; + if (!ds->queue_head) + ds->queue_tail = NULL; + talloc_free(dp); + ds->bw_bytes -= bytes; + } + + ds->back_pts = recompute_keyframe_target_pts(ds->queue_head); + } +} + static void execute_trackswitch(struct demux_internal *in) { in->tracks_switched = false; @@ -783,17 +898,24 @@ static struct demux_packet *dequeue_packet(struct demux_stream *ds) ds->attached_picture_added = true; return demux_copy_packet(ds->attached_picture); } - if (!ds->head) + if (!ds->reader_head) return NULL; - struct demux_packet *pkt = ds->head; - ds->head = pkt->next; + struct demux_packet *pkt = ds->reader_head; + ds->reader_head = pkt->next; + + // Update cached packet queue state. + ds->fw_packs--; + size_t bytes = demux_packet_estimate_total_size(pkt); + ds->fw_bytes -= bytes; + ds->bw_bytes += bytes; + + // The returned packet is mutated etc. and will be owned by the user. + pkt = demux_copy_packet(pkt); + if (!pkt) + abort(); pkt->next = NULL; - if (!ds->head) - ds->tail = NULL; - ds->bytes -= demux_packet_estimate_total_size(pkt); - ds->packs--; - double ts = pkt->dts == MP_NOPTS_VALUE ? pkt->pts : pkt->dts; + double ts = PTS_OR_DEF(pkt->dts, pkt->pts); if (ts != MP_NOPTS_VALUE) ds->base_ts = ts; @@ -823,6 +945,7 @@ static struct demux_packet *dequeue_packet(struct demux_stream *ds) pkt->start = MP_ADD_PTS(pkt->start, ds->in->ts_offset); pkt->end = MP_ADD_PTS(pkt->end, ds->in->ts_offset); + prune_old_packets(ds->in); return pkt; } @@ -862,7 +985,7 @@ struct demux_packet *demux_read_packet(struct sh_stream *sh) const char *t = stream_type_name(ds->type); MP_DBG(in, "reading packet for %s\n", t); in->eof = false; // force retry - while (ds->selected && !ds->head) { + while (ds->selected && !ds->reader_head) { ds->active = true; // Note: the following code marks EOF if it can't continue if (in->threading) { @@ -927,7 +1050,7 @@ bool demux_has_packet(struct sh_stream *sh) bool has_packet = false; if (sh) { pthread_mutex_lock(&sh->ds->in->lock); - has_packet = sh->ds->head; + has_packet = sh->ds->reader_head; pthread_mutex_unlock(&sh->ds->in->lock); } return has_packet; @@ -1272,8 +1395,9 @@ static struct demuxer *open_given_type(struct mpv_global *global, .d_buffer = talloc(demuxer, struct demuxer), .d_user = demuxer, .min_secs = opts->min_secs, - .max_packs = opts->max_packs, .max_bytes = opts->max_bytes, + .max_bytes_bw = opts->max_bytes_bw, + .seekable_cache = opts->seekable_cache, .initial_state = true, }; pthread_mutex_init(&in->lock, NULL); @@ -1426,109 +1550,157 @@ struct demuxer *demux_open_url(const char *url, return d; } -static void flush_locked_state(demuxer_t *demuxer) +// called locked, from user thread only +static void clear_reader_state(struct demux_internal *in) { - demuxer->in->warned_queue_overflow = false; - demuxer->in->eof = false; - demuxer->in->last_eof = false; - demuxer->in->idle = true; - demuxer->filepos = -1; // implicitly synchronized + for (int n = 0; n < in->num_streams; n++) + ds_clear_reader_state(in->streams[n]->ds); + in->warned_queue_overflow = false; + in->d_user->filepos = -1; // implicitly synchronized } -static void flush_locked(demuxer_t *demuxer) +static void clear_demux_state(struct demux_internal *in) { - for (int n = 0; n < demuxer->in->num_streams; n++) - ds_flush(demuxer->in->streams[n]->ds); - flush_locked_state(demuxer); + clear_reader_state(in); + for (int n = 0; n < in->num_streams; n++) + ds_clear_demux_state(in->streams[n]->ds); + in->eof = false; + in->last_eof = false; + in->idle = true; } // clear the packet queues void demux_flush(demuxer_t *demuxer) { pthread_mutex_lock(&demuxer->in->lock); - flush_locked(demuxer); + clear_demux_state(demuxer->in); pthread_mutex_unlock(&demuxer->in->lock); } -static bool demux_seek_cache_maybe(demuxer_t *demuxer, double seek_pts, int flags) +static void recompute_buffers(struct demux_stream *ds) { - struct demux_internal *in = demuxer->in; - struct demux_ctrl_reader_state rstate; - bool seeked = true; - double seek_pts_offset = MP_ADD_PTS(seek_pts, -in->ts_offset); - assert(demuxer == in->d_user); + ds->fw_packs = 0; + ds->fw_bytes = 0; + ds->bw_bytes = 0; + + bool in_backbuffer = true; + for (struct demux_packet *dp = ds->queue_head; dp; dp = dp->next) { + if (dp == ds->reader_head) + in_backbuffer = false; + + size_t bytes = demux_packet_estimate_total_size(dp); + if (in_backbuffer) { + ds->bw_bytes += bytes; + } else { + ds->fw_packs++; + ds->fw_bytes += bytes; + } + } +} - if ((flags & SEEK_FACTOR)) +static struct demux_packet *find_seek_target(struct demux_stream *ds, + double pts, int flags) +{ + struct demux_packet *target = NULL; + double target_diff = MP_NOPTS_VALUE; + for (struct demux_packet *dp = ds->queue_head; dp; dp = dp->next) { + if (!dp->keyframe) + continue; + + double range_pts = recompute_keyframe_target_pts(dp); + if (range_pts == MP_NOPTS_VALUE) + continue; + + double diff = pts - range_pts; + if (flags & SEEK_BACKWARD) + diff = -diff; + if (target_diff != MP_NOPTS_VALUE) { + if (diff <= 0) { + if (target_diff <= 0 && diff <= target_diff) + continue; + } else if (diff >= target_diff) + continue; + } + target_diff = diff; + target = dp; + } + + return target; +} + +// must be called locked +static bool try_seek_cache(struct demux_internal *in, double pts, int flags) +{ + if ((flags & SEEK_FACTOR) || !in->seekable_cache) return false; + // no idea how this could interact + if (in->seeking) + return false; + + struct demux_ctrl_reader_state rstate; if (cached_demux_control(in, DEMUXER_CTRL_GET_READER_STATE, &rstate) < 0) return false; - MP_VERBOSE(in, "in-cache seek range = %f <-> %f (%f)\n", - rstate.ts_range[0], rstate.ts_range[1], seek_pts); + double start = MP_ADD_PTS(rstate.ts_min, -in->ts_offset); + double end = MP_ADD_PTS(rstate.ts_max, -in->ts_offset); - if (seek_pts < rstate.ts_range[0] || - seek_pts > rstate.ts_range[1]) + MP_VERBOSE(in, "in-cache seek range = %f <-> %f (%f)\n", start, end, pts); + + if (pts < start || pts > end) return false; MP_VERBOSE(in, "in-cache seek is possible..\n"); - for (int n = 0; n < in->num_streams; n++) { - struct demux_stream *ds = in->streams[n]->ds; - demux_packet_t *dp = ds->head; - bool found_keyframe = false; - int idx = 0; - if (!ds->active || (ds->eof && !ds->head)) - continue; - // find idx of pts - while (dp) { - demux_packet_t *dn = dp->next; - if (dp->pts >= seek_pts_offset) - break; - idx++; - dp = dn; - } - if ((flags & SEEK_FORWARD)) { - // increment idx until keyframe - while (dp) { - demux_packet_t *dn = dp->next; - if (dp->keyframe) { - found_keyframe = true; - break; - } - idx++; - dp = dn; - } - } else { - // find last keyframe before idx - int i = 0, key_idx = 0; - dp = ds->head; - while (dp && i < idx) { - demux_packet_t *dn = dp->next; - if (dp->keyframe) { - found_keyframe = true; - key_idx = i; + clear_reader_state(in); + + // Adjust the seek target to the found video key frames. Otherwise the + // video will undershoot the seek target, while audio will be closer to it. + // The player frontend will play the additional video without audio, so + // you get silent audio for the amount of "undershoot". Adjusting the seek + // target will make the audio seek to the video target or before. + // (If hr-seeks are used, it's better to skip this, as it would only mean + // that more audio data than necessary would have to be decoded.) + if (!(flags & SEEK_HR)) { + for (int n = 0; n < in->num_streams; n++) { + struct demux_stream *ds = in->streams[n]->ds; + if (ds->selected && ds->type == STREAM_VIDEO) { + struct demux_packet *target = find_seek_target(ds, pts, flags); + if (target) { + double target_pts = recompute_keyframe_target_pts(target); + if (target_pts != MP_NOPTS_VALUE) { + MP_VERBOSE(in, "adjust seek target %f -> %f\n", + pts, target_pts); + // (We assume the find_seek_target() will return the + // same target for the video stream.) + pts = target_pts; + flags &= SEEK_FORWARD; + flags |= SEEK_BACKWARD; + } } - i++; - dp = dn; + break; } - idx = key_idx; } + } - if (!found_keyframe) { - seeked = false; - break; - } + for (int n = 0; n < in->num_streams; n++) { + struct demux_stream *ds = in->streams[n]->ds; - while (idx-- > 0) { - demux_packet_t *pkt = dequeue_packet(ds); - free_demux_packet(pkt); - } + struct demux_packet *target = find_seek_target(ds, pts, flags); + ds->reader_head = target; + recompute_buffers(ds); + + MP_VERBOSE(in, "seeking stream %d (%s) to ", + n, stream_type_name(ds->type)); - assert(ds->head && ds->head->keyframe); + if (target) { + MP_VERBOSE(in, "packet %f/%f\n", target->pts, target->dts); + } else { + MP_VERBOSE(in, "nothing\n"); + } } - return seeked; + return true; } int demux_seek(demuxer_t *demuxer, double seek_pts, int flags) @@ -1552,16 +1724,17 @@ int demux_seek(demuxer_t *demuxer, double seek_pts, int flags) MP_VERBOSE(in, "queuing seek to %f%s\n", seek_pts, in->seeking ? " (cascade)" : ""); - if (demux_seek_cache_maybe(demuxer, seek_pts, flags)) { + if (!(flags & SEEK_FACTOR)) + seek_pts = MP_ADD_PTS(seek_pts, -in->ts_offset); + + if (try_seek_cache(in, seek_pts, flags)) { MP_VERBOSE(in, "in-cache seek worked!\n"); - flush_locked_state(demuxer); } else { - flush_locked(demuxer); + clear_demux_state(in); + in->seeking = true; in->seek_flags = flags; in->seek_pts = seek_pts; - if (!(flags & SEEK_FACTOR)) - in->seek_pts = MP_ADD_PTS(in->seek_pts, -in->ts_offset); if (!in->threading) execute_seek(in); @@ -1596,7 +1769,7 @@ void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream, // don't flush buffers if stream is already selected / unselected if (stream->ds->selected != selected) { stream->ds->selected = selected; - ds_flush(stream->ds); + ds_clear_demux_state(stream->ds); in->tracks_switched = true; stream->ds->need_refresh = selected && !in->initial_state; if (stream->ds->need_refresh) @@ -1616,6 +1789,10 @@ void demux_set_stream_autoselect(struct demuxer *demuxer, bool autoselect) demuxer->in->autoselect = autoselect; } +// This is for demuxer implementations only. demuxer_select_track() sets the +// logical state, while this function returns the actual state (in case the +// demuxer attempts to cache even unselected packets for track switching - this +// will potentially be done in the future). bool demux_stream_is_selected(struct sh_stream *stream) { if (!stream) @@ -1757,27 +1934,42 @@ static int cached_demux_control(struct demux_internal *in, int cmd, void *arg) struct demux_ctrl_reader_state *r = arg; *r = (struct demux_ctrl_reader_state){ .eof = in->last_eof, - .ts_range = {MP_NOPTS_VALUE, MP_NOPTS_VALUE}, + .ts_start = MP_NOPTS_VALUE, + .ts_min = MP_NOPTS_VALUE, + .ts_max = MP_NOPTS_VALUE, + .ts_reader = MP_NOPTS_VALUE, .ts_duration = -1, }; - int num_packets = 0; for (int n = 0; n < in->num_streams; n++) { struct demux_stream *ds = in->streams[n]->ds; - if (ds->active && !(!ds->head && ds->eof) && !ds->ignore_eof) { - r->underrun |= !ds->head && !ds->eof; - r->ts_range[0] = MP_PTS_MAX(r->ts_range[0], ds->base_ts); - r->ts_range[1] = MP_PTS_MIN(r->ts_range[1], ds->last_ts); - num_packets += ds->packs; + if (ds->active && !(!ds->queue_head && ds->eof) && !ds->ignore_eof) + { + r->underrun |= !ds->reader_head && !ds->eof; + r->ts_reader = MP_PTS_MAX(r->ts_reader, ds->base_ts); + // (yes, this is asymmetric, and uses MAX in both cases - it's ok + // if it's a bit off for ts_max, as the demuxer can just wait for + // new packets if we seek there and also last_ts is the hightest + // DTS or PTS, while ts_min should be as accurate as possible, as + // we would have to trigger a real seek if it's off and we seeked + // there with SEEK_BACKWARD) + r->ts_max = MP_PTS_MAX(r->ts_max, ds->last_ts); + r->ts_min = MP_PTS_MAX(r->ts_min, ds->back_pts); + if (ds->queue_head) { + double ts = PTS_OR_DEF(ds->queue_head->dts, + ds->queue_head->pts); + r->ts_start = MP_PTS_MIN(r->ts_start, ts); + } } } r->idle = (in->idle && !r->underrun) || r->eof; r->underrun &= !r->idle; - if (r->ts_range[0] != MP_NOPTS_VALUE && r->ts_range[1] != MP_NOPTS_VALUE) - r->ts_duration = MPMAX(0, r->ts_range[1] - r->ts_range[0]); - if (!num_packets || in->seeking) - r->ts_duration = 0; - r->ts_range[0] = MP_ADD_PTS(r->ts_range[0], in->ts_offset); - r->ts_range[1] = MP_ADD_PTS(r->ts_range[1], in->ts_offset); + if (in->seeking) + r->ts_max = r->ts_min = MP_NOPTS_VALUE; + r->ts_start = MP_ADD_PTS(r->ts_start, in->ts_offset); + r->ts_min = MP_ADD_PTS(r->ts_min, in->ts_offset); + r->ts_max = MP_ADD_PTS(r->ts_max, in->ts_offset); + if (r->ts_reader != MP_NOPTS_VALUE && r->ts_reader <= r->ts_max) + r->ts_duration = r->ts_max - r->ts_reader; return CONTROL_OK; } } diff --git a/demux/demux.h b/demux/demux.h index 4fac2007a4..24a08ecff4 100644 --- a/demux/demux.h +++ b/demux/demux.h @@ -42,8 +42,14 @@ enum demux_ctrl { struct demux_ctrl_reader_state { bool eof, underrun, idle; - double ts_range[2]; // start, end double ts_duration; + double ts_reader; // approx. timerstamp of decoder position + double ts_start; // approx. timestamp for the earliest packet buffered + double ts_min; // timestamp of the earliest packet in backward cache + // that can be seeked to (i.e. all streams have such + // a packet for which SEEK_BACKWARD can be executed) + double ts_max; // timestamp of latest packet in forward cache that can be + // seeked to }; struct demux_ctrl_stream_ctrl { diff --git a/player/command.c b/player/command.c index f3ab31aec5..3b20ec8f3d 100644 --- a/player/command.c +++ b/player/command.c @@ -1696,11 +1696,10 @@ static int mp_property_demuxer_cache_time(void *ctx, struct m_property *prop, if (demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_READER_STATE, &s) < 1) return M_PROPERTY_UNAVAILABLE; - double ts = s.ts_range[1]; - if (ts == MP_NOPTS_VALUE) + if (s.ts_max == MP_NOPTS_VALUE) return M_PROPERTY_UNAVAILABLE; - return m_property_double_ro(action, arg, ts); + return m_property_double_ro(action, arg, s.ts_max); } static int mp_property_demuxer_cache_idle(void *ctx, struct m_property *prop, |