diff options
-rw-r--r-- | DOCS/man/options.rst | 191 | ||||
-rw-r--r-- | audio/aframe.c | 50 | ||||
-rw-r--r-- | audio/aframe.h | 4 | ||||
-rw-r--r-- | demux/demux.c | 425 | ||||
-rw-r--r-- | demux/demux.h | 1 | ||||
-rw-r--r-- | demux/packet.c | 2 | ||||
-rw-r--r-- | demux/packet.h | 4 | ||||
-rw-r--r-- | filters/f_decoder_wrapper.c | 128 | ||||
-rw-r--r-- | filters/f_decoder_wrapper.h | 1 | ||||
-rw-r--r-- | filters/frame.c | 20 | ||||
-rw-r--r-- | filters/frame.h | 3 | ||||
-rw-r--r-- | options/options.c | 8 | ||||
-rw-r--r-- | options/options.h | 3 | ||||
-rw-r--r-- | player/command.c | 15 | ||||
-rw-r--r-- | player/core.h | 1 | ||||
-rw-r--r-- | player/loadfile.c | 5 | ||||
-rw-r--r-- | player/main.c | 1 | ||||
-rw-r--r-- | player/playloop.c | 27 | ||||
-rw-r--r-- | sub/dec_sub.c | 12 | ||||
-rw-r--r-- | sub/dec_sub.h | 1 | ||||
-rw-r--r-- | video/mp_image.c | 13 | ||||
-rw-r--r-- | video/mp_image.h | 2 |
22 files changed, 874 insertions, 43 deletions
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 8da721c834..f49b5f4f46 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -366,6 +366,197 @@ Playback Control of them fails. This doesn't affect playback of audio-only or video-only files. +``--play-direction=<forward|backward>`` + Control the playback direction (default: forward). Setting ``backward`` + will attempt to play the file in reverse direction, with decreasing + playback time. If this is set on playback starts, playback will start from + the end of the file. If this is changed at during playback, a hr-seek will + be issued to change the direction. + + The rest of this option description pertains to the ``backward`` mode. + + .. note:: + + Backward playback is extremely fragile. It may not always work, is much + slower than forward playback, and breaks certain other features. How + well it works depends mainly on the file being played. Generally, it + will show good results (or results at all) only if the stars align. + + mpv, as well as most media formats, were designed for forward playback + only. Backward playback is bolted on top of mpv, and tries to make a medium + effort to make backward playback work. Depending on your use-case, another + tool may work much better. + + Backward playback is not exactly a 1st class feature. Implementation + tradeoffs were made, that are bad for backward playback, but in turn do not + cause disadvantages for normal playback. Various possible optimizations are + not implemented in order to keep the complexity down. Normally, a media + player is highly pipelined (future data is prepared in separate threads, so + it is available in realtime when the next stage needs it), but backward + playback will essentially stall the pipeline at various random points. + + For example, for intra-only codecs are trivially backward playable, and + tools built around them may make efficient use of them (consider video + editors or camera viewers). mpv won't be efficient in this case, because it + uses its generic backward playback algorithm, that on top of it is not very + optimized. + + If you just want to quickly go backward through the video and just show + "keyframes", just use forward playback, and hold down the left cursor key + (which on CLI with default config sends many small relative seek commands). + + The implementation consists of mostly 3 parts: + + - Backward demuxing. This relies on the demuxer cache, so the demuxer cache + should (or must, didn't test it) be enabled, and its size will affect + performance. If the cache is too small or too large, quadratic runtime + behavior may result. + + - Backward decoding. The decoder library used (libavcodec) does not support + this. It is emulated by feeding bits of data in forward, putting the + result in a queue, returning the queue data to the VO in reverse, and + then starting over at an earlier position. This can require buffering an + extreme amount of decoded data, and also completely breaks pipelining. + + - Backward output. This is relatively simple, because the decoder returns + the frames in the needed order. However, this may cause various problems + because very basic assumptions are broken (such as time going forward). + Also, some filtering becomes impossible. Deinterlacing filters will not + work. + + Known problems: + + - It's fragile. If anything doesn't work, random non-useful behavior may + occur. In simple cases, the player will just play nonsense and artifacts. + In other cases, it may get stuck or heat the CPU. (Exceeding memory usage + significantly beyond the user-set limits would be a bug, though.) + + - Performance and resource usage isn't good. In part this is inherent to + backward playback of normal media formats, and in parts due to + implementation choices and tradeoffs. + + - This is extremely reliant on good demuxer behavior. Although backward + demuxing requires no special demuxer support, it is required that the + demuxer performs seeks reliably, fulfills some specific requirements + about packet metadata, and has deterministic behavior. + + - Starting playback exactly from the end may or may not work, depending on + seeking behavior and file duration detection. + + - Some container formats, audio, and video codecs are not supported due to + their behavior. There is no list, and the player usually does not detect + them. Certain live streams (including TV captures) may exhibit problems + in particular, as well as some lossy audio codecs. h264 intra-refresh is + known not to work due to problems with libavcodec. + + - Function with EDL/mkv ordered chapters is obviously broken. + + - Backward demuxing of subtitles is not supported. Subtitle display still + works for some external text subtitle formats. (These are fully read into + memory, and only backward display is needed.) Text subtitles that are + cached in the subtitle renderer also have a chance to be displayed + correctly. + + - Some features dealing with playback of broken or hard to deal with files + will be disabled (such as timestamp correction). + + - If demuxer low level seeks (i.e. seeking the actual demuxer instead of + just within the demuxer cache) are performed by backward playback, the + created seek ranges may not join, because not enough overlap is achieved. + + - Trying to use this with hardware video decoding will probably exhaust all + your GPU memory and then crash a thing or two. + + - Stream recording and encoding are broken. + + - Relative seeks may behave weird. Small seeks backward (towards smaller + time, i.e. ``seek -1``) may not really seek properly, and audio will + remain muted for a while. Using hr-seek is recommended, which should have + none of these problems. + + - Some things are just weird. For example, while seek commands manipulate + playback time in the expected way (provided they work correctly), the + framestep commands are transposed. Backstepping will perform very + expensive work to step forward by 1 frame. + + Tuning: + + - Remove all ``--vf``/``--af`` filters you have set. Disable deinterlacing. + Disable idiotic nonsense like SPDIF passthrough. + + - Increasing ``--video-reversal-buffer`` might help if reversal queue + overflow is reported, which may happen in high bitrate video, or video + with large GOP. + + - The demuxer cache is essential for backward demuxing. If it's too small, + a queue overflow will be logged, and backward playback cannot continue, + or it performs too many low level seeks. If it's too large, implementation + tradeoffs may cause general performance issues. Use ``--demuxer-max-bytes`` + to potentially increase the amount of packets the demuxer layer can queue + for reverse demuxing (basically it's the ``--video-reversal-buffer`` + equivalent for the demuxer layer). + + - ``--demuxer-backward-playback-step`` also factors into how many seeks may + be performed, and whether backward demuxing could break due to queue + overflow. + + - Setting ``--demuxer-cache-wait`` may be useful to cache the entire file + into the demuxer cache. Set ``--demuxer-max-bytes`` to a large size to + make sure it can read the entire cache; ``--demuxer-max-back-bytes`` + should also be set to a large size to prevent that tries to trim the + cache. + + - If audio artifacts are audible, even though the AO does not underrun, + increasing ``--audio-reversal-buffer`` might help in some cases. + +``--video-reversal-buffer=<bytesize>``, ``--audio-reversal-buffer=<bytesize>`` + For backward decoding. Backward decoding decodes forward in steps, and then + reverses the decoder output. These options control the approximate maximum + amount of bytes that can be buffered. The main use of this is to avoid + unbounded resource usage; during normal backward playback, it's not supposed + to hit the limit, and if it does, it will drop frames and complain about it. + + This does not work correctly if video hardware decoding is used. The video + frame size will not include the referenced GPU and driver memory. + + How large the queue size needs to be depends entirely on the way the media + was encoded. Audio typically requires a very small buffer, while video can + require excessively large buffers. + + (Technically, this allows the last frame to exceed the limit. Also, this + does not account for other buffered frames, such as inside the decoder or + the video output.) + + This does not affect demuxer cache behavior at all. + + See ``--list-options`` for defaults and value range. ``<bytesize>`` options + accept suffixes such as ``KiB`` and ``MiB``. + +``--video-backward-overlap=<auto|number>``, ``--audio-backward-overlap=<auto|number>`` + Number of overlapping packets to use for backward decoding (default: auto). + Backward decoding works by forward decoding in small steps. Some codecs + cannot restart decoding from any packet (even if it's marked as seek point), + which becomes noticeable with backward decoding (in theory this is a problem + with seeking too, but ``--hr-seek-demuxer-offset`` can fix it for seeking). + In particular, MDCT based audio codecs are affected. + + The solution is to feed a previous packet to the decoder each time, and then + discard the output. This option controls how many packets to feed. The + ``auto`` choice is currently hardcoded to 1 for audio, and 0 for video. + + ``--video-backward-overlap`` was intended to handle intra-refresh video, but + which does not work since libavcodec silently drops frames even with + ``--vd-lavc-show-all``, and it's too messy to accurately guess which frames + have been dropped. + +``--demuxer-backward-playback-step=<seconds>`` + Number of seconds the demuxer should seek back to get new packets during + backward playback (default: 60). This is useful for tuning backward + playback, see ``--play-direction`` for details. + + Setting this to a very low value or 0 may make the player think seeking is + broken, or may make it perform multiple seeks. + Program Behavior ---------------- diff --git a/audio/aframe.c b/audio/aframe.c index cb5d412f98..bc43bc98d2 100644 --- a/audio/aframe.c +++ b/audio/aframe.c @@ -520,6 +520,56 @@ bool mp_aframe_set_silence(struct mp_aframe *f, int offset, int samples) return true; } +bool mp_aframe_reverse(struct mp_aframe *f) +{ + int format = mp_aframe_get_format(f); + size_t bps = af_fmt_to_bytes(format); + if (!af_fmt_is_pcm(format) || bps > 16) + return false; + + uint8_t **d = mp_aframe_get_data_rw(f); + if (!d) + return false; + + int planes = mp_aframe_get_planes(f); + int samples = mp_aframe_get_size(f); + int channels = mp_aframe_get_channels(f); + size_t sstride = mp_aframe_get_sstride(f); + + int plane_samples = channels; + if (af_fmt_is_planar(format)) + plane_samples = 1; + + for (int p = 0; p < planes; p++) { + for (int n = 0; n < samples / 2; n++) { + int s1_offset = n * sstride; + int s2_offset = (samples - 1 - n) * sstride; + for (int c = 0; c < plane_samples; c++) { + // Nobody said it'd be fast. + char tmp[16]; + uint8_t *s1 = d[p] + s1_offset + c * bps; + uint8_t *s2 = d[p] + s2_offset + c * bps; + memcpy(tmp, s2, bps); + memcpy(s2, s1, bps); + memcpy(s1, tmp, bps); + } + } + } + + return true; +} + +int mp_aframe_approx_byte_size(struct mp_aframe *frame) +{ + // God damn, AVFrame is too fucking annoying. Just go with the size that + // allocating a new frame would use. + int planes = mp_aframe_get_planes(frame); + size_t sstride = mp_aframe_get_sstride(frame); + int samples = frame->av_frame->nb_samples; + int plane_size = MP_ALIGN_UP(sstride * MPMAX(samples, 1), 32); + return plane_size * planes + sizeof(*frame); +} + struct mp_aframe_pool { AVBufferPool *avpool; int element_size; diff --git a/audio/aframe.h b/audio/aframe.h index ed92c223f6..21d4494f5f 100644 --- a/audio/aframe.h +++ b/audio/aframe.h @@ -51,6 +51,10 @@ int mp_aframe_get_planes(struct mp_aframe *frame); int mp_aframe_get_total_plane_samples(struct mp_aframe *frame); size_t mp_aframe_get_sstride(struct mp_aframe *frame); +bool mp_aframe_reverse(struct mp_aframe *frame); + +int mp_aframe_approx_byte_size(struct mp_aframe *frame); + char *mp_aframe_format_str_buf(char *buf, size_t buf_size, struct mp_aframe *fmt); #define mp_aframe_format_str(fmt) mp_aframe_format_str_buf((char[32]){0}, 32, (fmt)) diff --git a/demux/demux.c b/demux/demux.c index ae6d3a96f6..92121c9b7b 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -88,6 +88,9 @@ struct demux_opts { int seekable_cache; int create_ccs; char *record_file; + int video_back_preroll; + int audio_back_preroll; + double back_seek_size; }; #define OPT_BASE_STRUCT struct demux_opts @@ -110,6 +113,12 @@ const struct m_sub_options demux_conf = { ({"auto", -1}, {"no", 0}, {"yes", 1})), OPT_FLAG("sub-create-cc-track", create_ccs, 0), OPT_STRING("stream-record", record_file, 0), + OPT_CHOICE_OR_INT("video-backward-overlap", video_back_preroll, 0, 0, + 1024, ({"auto", -1})), + OPT_CHOICE_OR_INT("audio-backward-overlap", audio_back_preroll, 0, 0, + 1024, ({"auto", -1})), + OPT_DOUBLE("demuxer-backward-playback-step", back_seek_size, M_OPT_MIN, + .min = 0), {0} }, .size = sizeof(struct demux_opts), @@ -121,6 +130,9 @@ const struct m_sub_options demux_conf = { .min_secs_cache = 10.0 * 60 * 60, .seekable_cache = -1, .access_references = 1, + .video_back_preroll = -1, + .audio_back_preroll = -1, + .back_seek_size = 60, }, }; @@ -183,6 +195,15 @@ struct demux_internal { // file (or if the demuxer was just opened). bool after_seek_to_start; + // Demuxing backwards. Since demuxer implementations don't support this + // directly, it is emulated by seeking backwards for every packet run. Also, + // packets between keyframes are demuxed forwards (you can't decode that + // stuff otherwise), which adds complexity on top of it. + bool back_demuxing; + + // For backward demuxing: back-step seek needs to be triggered. + bool need_back_seek; + bool tracks_switched; // thread needs to inform demuxer of this bool seeking; // there's a seek queued @@ -322,6 +343,27 @@ struct demux_stream { int64_t last_ret_pos; double last_ret_dts; + // Backwards demuxing. + // pos/dts of the previous keyframe packet returned; valid if + // back_range_started or back_restarting are set. + int64_t back_restart_pos; + double back_restart_dts; + bool back_restarting; // searching keyframe before restart pos + // Current PTS lower bound for back demuxing. + double back_seek_pos; + // pos/dts of the packet to resume demuxing from when another stream caused + // a seek backward to get more packets. reader_head will be reset to this + // packet as soon as it's encountered again. + int64_t back_resume_pos; + double back_resume_dts; + bool back_resuming; // resuming mode (above fields are valid/used) + // Set to true if the first packet (keyframe) of a range was returned. + bool back_range_started; + // Number of packets at start of range yet to return. -1 is used for BOF. + int back_range_min; + // Static packet preroll count. + int back_preroll; + // for closed captions (demuxer_feed_caption) struct sh_stream *cc; bool ignore_eof; // ignore stream in underrun detection @@ -352,6 +394,9 @@ static void demuxer_sort_chapters(demuxer_t *demuxer); static void *demux_thread(void *pctx); static void update_cache(struct demux_internal *in); static void add_packet_locked(struct sh_stream *stream, demux_packet_t *dp); +static struct demux_packet *advance_reader_head(struct demux_stream *ds); +static bool queue_seek(struct demux_internal *in, double seek_pts, int flags, + bool clear_back_state); #if 0 // very expensive check for redundant cached queue state @@ -693,7 +738,8 @@ static void ds_clear_reader_queue_state(struct demux_stream *ds) ds->need_wakeup = true; } -static void ds_clear_reader_state(struct demux_stream *ds) +static void ds_clear_reader_state(struct demux_stream *ds, + bool clear_back_state) { ds_clear_reader_queue_state(ds); @@ -704,6 +750,18 @@ static void ds_clear_reader_state(struct demux_stream *ds) ds->attached_picture_added = false; ds->last_ret_pos = -1; ds->last_ret_dts = MP_NOPTS_VALUE; + + if (clear_back_state) { + ds->back_restart_pos = -1; + ds->back_restart_dts = MP_NOPTS_VALUE; + ds->back_restarting = false; + ds->back_seek_pos = MP_NOPTS_VALUE; + ds->back_resume_pos = -1; + ds->back_resume_dts = MP_NOPTS_VALUE; + ds->back_resuming = false; + ds->back_range_started = false; + ds->back_range_min = 0; + } } // Call if the observed reader state on this stream somehow changes. The wakeup @@ -728,7 +786,7 @@ static void update_stream_selection_state(struct demux_internal *in, ds->eof = false; ds->refreshing = false; - ds_clear_reader_state(ds); + ds_clear_reader_state(ds, true); // We still have to go over the whole stream list to update ds->eager for // other streams too, because they depend on other stream's selections. @@ -859,6 +917,8 @@ static void demux_add_sh_stream_locked(struct demux_internal *in, }; talloc_set_destructor(sh->ds, ds_destroy); + struct demux_stream *ds = sh->ds; + if (!sh->codec->codec) sh->codec->codec = ""; @@ -887,6 +947,19 @@ static void demux_add_sh_stream_locked(struct demux_internal *in, mp_tags_replace(sh->ds->tags_init->sh, sh->tags); mp_packet_tags_setref(&sh->ds->tags_reader, sh->ds->tags_init); + switch (ds->type) { + case STREAM_AUDIO: + ds->back_preroll = in->opts->audio_back_preroll; + if (ds->back_preroll < 0) + ds->back_preroll = 1; // auto + break; + case STREAM_VIDEO: + ds->back_preroll = in->opts->video_back_preroll; + if (ds->back_preroll < 0) + ds->back_preroll = 0; // auto + break; + } + in->events |= DEMUX_EVENT_STREAMS; if (in->wakeup_cb) in->wakeup_cb(in->wakeup_cb_ctx); @@ -1159,7 +1232,241 @@ void demuxer_feed_caption(struct sh_stream *stream, demux_packet_t *dp) dp->dts = MP_ADD_PTS(dp->dts, -in->ts_offset); add_packet_locked(sh, dp); pthread_mutex_unlock(&in->lock); +} + +static void perform_backward_seek(struct demux_internal *in) +{ + double target = MP_NOPTS_VALUE; + + for (int n = 0; n < in->num_streams; n++) { + struct demux_stream *ds = in->streams[n]->ds; + + if (ds->reader_head && !ds->back_restarting && !ds->back_resuming && + ds->eager) + { + ds->back_resuming = true; + ds->back_resume_pos = ds->reader_head->pos; + ds->back_resume_dts = ds->reader_head->dts; + } + target = MP_PTS_MIN(target, ds->back_seek_pos); + } + + target = PTS_OR_DEF(target, in->d_thread->start_time); + + target -= in->opts->back_seek_size; + + MP_VERBOSE(in, "triggering backward seek to get more packets\n"); + queue_seek(in, target, SEEK_SATAN, false); + in->reading = true; +} + +// Search for a packet to resume demuxing from. +// from_cache: if true, this was called trying to go backwards in the cache; +// if false, this is from a hard seek before the back_restart_pos +// The implementation of this function is quite awkward, because the packet +// queue is a singly linked list without back links, while it needs to search +// backwards. +// This is the core of backward demuxing. +static void find_backward_restart_pos(struct demux_stream *ds, bool from_cache) +{ + struct demux_internal *in = ds->in; + + assert(ds->back_restarting); + + if (!ds->reader_head) + return; // no packets yet + + struct demux_packet *first = ds->reader_head; + struct demux_packet *last = ds->queue->tail; + assert(last); + + if ((ds->global_correct_dts && last->dts < ds->back_restart_dts) || + (ds->global_correct_pos && last->pos < ds->back_restart_pos)) + return; // restart pos not reached yet + + // The target we're searching for is apparently before the start of the queue. + if ((ds->global_correct_dts && first->dts > ds->back_restart_dts) || + (ds->global_correct_pos && first->pos > ds->back_restart_pos)) + { + // If this function was called for trying to backstep within the packet + // cache, the cache probably got pruned past the target (reader_head is + // being moved backwards, so nothing stops it from pruning packets + // before that). Just make the caller seek. + if (from_cache) { + in->need_back_seek = true; + return; + } + + // The demuxer probably seeked to the wrong position, or broke dts/pos + // determinism assumptions? + MP_ERR(in, "Demuxer did not seek correctly.\n"); + return; + } + + // Packet at back_restart_pos. (Note: we don't actually need it, only the + // packet immediately before it. But same effort.) + struct demux_packet *back_restart = NULL; + + for (struct demux_packet *cur = first; cur; cur = cur->next) { + if ((ds->global_correct_dts && cur->dts == ds->back_restart_dts) || + (ds->global_correct_pos && cur->pos == ds->back_restart_pos)) + { + back_restart = cur; + break; + } + } + + if (!back_restart) { + // The packet should have been in the searched range; maybe dts/pos + // determinism assumptions were broken. + MP_ERR(in, "Demuxer not cooperating.\n"); + return; + } + + if (!ds->reader_head->keyframe) + MP_WARN(in, "Queue not starting on keyframe.\n"); + + // Find where to restart demuxing. It's usually the last keyframe packet + // before restart_pos, but might be up to back_preroll packets earlier. + + struct demux_packet *last_keyframe = NULL; + struct demux_packet *last_preroll = NULL; + + // Keep this packet at back_preroll packets before last_keyframe. + struct demux_packet *pre_packet = ds->reader_head; + int pre_packet_offset = ds->back_preroll; + + // (Normally, we'd just iterate backwards, but no back links.) + for (struct demux_packet *cur = ds->reader_head; + cur != back_restart; + cur = cur->next) + { + if (cur->keyframe) { + last_keyframe = cur; + last_preroll = pre_packet; + } + + if (pre_packet_offset) { + pre_packet_offset--; + } else { + pre_packet = pre_packet->next; + } + } + + if (!last_keyframe) { + // Note: assume this holds true. You could think of various reasons why + // this might break. + if (ds->queue->is_bof) { + MP_VERBOSE(in, "BOF for stream %d\n", ds->index); + ds->back_restarting = false; + ds->back_range_started = false; + ds->back_range_min = -1; + ds->need_wakeup = true; + wakeup_ds(ds); + return; + } + goto resume_earlier; + } + + int got_preroll = 0; + for (struct demux_packet *cur = last_preroll; + cur != last_keyframe; + cur = cur->next) + got_preroll++; + + if (got_preroll < ds->back_preroll && !ds->queue->is_bof) + goto resume_earlier; + + // (Round preroll down to last_keyframe in the worst case.) + while (!last_preroll->keyframe) + last_preroll = last_preroll->next; + + // Skip reader_head from previous keyframe to current one. + // Or if preroll is involved, the first preroll packet. + while (ds->reader_head != last_preroll) { + if (!advance_reader_head(ds)) + assert(0); // last_preroll must be in list + } + + ds->back_restarting = false; + ds->back_range_started = false; + ds->back_range_min = got_preroll + 1; + ds->need_wakeup = true; + wakeup_ds(ds); + return; + +resume_earlier: + // If an earlier seek didn't land at an early enough position, we need to + // try to seek even earlier. Usually this will happen with large + // back_preroll values, because the initial back seek does not take them + // into account. We don't really know how much we need to seek, so add some + // random value to the previous seek value. Not ideal. + if (!from_cache && ds->back_seek_pos != MP_NOPTS_VALUE) + ds->back_seek_pos -= 1.0; + in->need_back_seek = true; +} + +// Process that one or multiple packets were added. +static void back_demux_see_packets(struct demux_stream *ds) +{ + struct demux_internal *in = ds->in; + + if (!ds->selected || !in->back_demuxing) + return; + + assert(!(ds->back_resuming && ds->back_restarting)); + + if (!ds->global_correct_dts && !ds->global_correct_pos) { + MP_ERR(in, "Can't demux backward due to demuxer problems.\n"); + return; + } + + while (ds->back_resuming && ds->reader_head) { + struct demux_packet *head = ds->reader_head; + if ((ds->global_correct_dts && head->dts == ds->back_resume_dts) || + (ds->global_correct_pos && head->pos == ds->back_resume_pos)) + { + ds->back_resuming = false; + ds->need_wakeup = true; + wakeup_ds(ds); // probably + break; + } + advance_reader_head(ds); + } + + if (ds->back_restarting) + find_backward_restart_pos(ds, false); +} + +// Resume demuxing from an earlier position for backward playback. May trigger +// a seek. +static void step_backwards(struct demux_stream *ds) +{ + struct demux_internal *in = ds->in; + + assert(in->back_demuxing); + + assert(!ds->back_restarting); + ds->back_restarting = true; + + // Move to start of queue. This is inefficient, because we need to iterate + // the entire fucking packet queue just to update the fw_* stats. But as + // long as we don't have demux_packet.prev links or a complete index, it's + // the thing to do. + // Note: if the buffer forward is much larger than the one backward, it + // would be worth looping until the previous reader_head and decrementing + // fw_packs/fw_bytes - you could skip the full recompute_buffers(). + ds->reader_head = ds->queue->head; + in->fw_bytes -= ds->fw_bytes; + recompute_buffers(ds); + in->fw_bytes += ds->fw_bytes; + + // Exclude weird special-cases (incomplete pruning? broken seeks?) + while (ds->reader_head && !ds->reader_head->keyframe) + advance_reader_head(ds); + + find_backward_restart_pos(ds, true); } // Add the keyframe to the end of the index. Not all packets are actually added. @@ -1353,6 +1660,9 @@ static void attempt_range_joining(struct demux_internal *in) MP_VERBOSE(in, "ranges joined!\n"); + for (int n = 0; n < in->num_streams; n++) + back_demux_see_packets(in->streams[n]->ds); + failed: clear_cached_range(in, next); free_empty_cached_ranges(in); @@ -1560,6 +1870,8 @@ static void add_packet_locked(struct sh_stream *stream, demux_packet_t *dp) } } + back_demux_see_packets(ds); + wakeup_ds(ds); } @@ -1578,13 +1890,19 @@ static bool read_packet(struct demux_internal *in) bool read_more = false, prefetch_more = false, refresh_more = false; for (int n = 0; n < in->num_streams; n++) { struct demux_stream *ds = in->streams[n]->ds; - read_more |= ds->eager && !ds->reader_head; + if (ds->eager) { + read_more |= !ds->reader_head; + if (in->back_demuxing) + read_more |= ds->back_restarting || ds->back_resuming; + } refresh_more |= ds->refreshing; if (ds->eager && ds->queue->last_ts != MP_NOPTS_VALUE && in->min_secs > 0 && ds->base_ts != MP_NOPTS_VALUE && - ds->queue->last_ts >= ds->base_ts) + ds->queue->last_ts >= ds->base_ts && + !in->back_demuxing) prefetch_more |= ds->queue->last_ts - ds->base_ts < in->min_secs; } + MP_TRACE(in, "bytes=%zd, read_more=%d prefetch_more=%d, refresh_more=%d\n", in->fw_bytes, read_more, prefetch_more, refresh_more); if (in->fw_bytes >= in->max_bytes) { @@ -1604,6 +1922,8 @@ static bool read_packet(struct demux_internal *in) ds->refreshing ? " (refreshing)" : ""); } } + if (in->back_demuxing) + MP_ERR(in, "Backward playback is likely stuck/broken now.\n"); } for (int n = 0; n < in->num_streams; n++) { struct demux_stream *ds = in->streams[n]->ds; @@ -1798,6 +2118,10 @@ static bool thread_work(struct demux_internal *in) execute_trackswitch(in); return true; } + if (in->need_back_seek) { + perform_backward_seek(in); + return true; + } if (in->seeking) { execute_seek(in); return true; @@ -1876,6 +2200,11 @@ static int dequeue_packet(struct demux_stream *ds, struct demux_packet **res) if (in->blocked) return 0; + if (ds->back_resuming || ds->back_restarting) { + assert(in->back_demuxing); + return 0; + } + if (ds->sh->attached_picture) { ds->eof = true; if (ds->attached_picture_added) @@ -1895,8 +2224,27 @@ static int dequeue_packet(struct demux_stream *ds, struct demux_packet **res) pthread_cond_signal(&in->wakeup); // possibly read more } + bool eof = !ds->reader_head && ds->eof; + + if (in->back_demuxing) { + // Subtitles not supported => EOF. + if (!ds->eager) + return -1; + + // Next keyframe (or EOF) was reached => step back. + if (ds->back_range_started && !ds->back_range_min && + ((ds->reader_head && ds->reader_head->keyframe) || eof)) + { + step_backwards(ds); + if (ds->back_restarting) + return 0; + } + + eof = ds->back_range_min < 0; + } + ds->need_wakeup = !ds->reader_head; - if (!ds->reader_head) { + if (!ds->reader_head || eof) { if (!ds->eager) { // Non-eager streams temporarily return EOF. If they returned 0, // the reader would have to wait for new packets, which does not @@ -1904,7 +2252,7 @@ static int dequeue_packet(struct demux_stream *ds, struct |