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=`` + 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=``, ``--audio-reversal-buffer=`` + 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. ```` options + accept suffixes such as ``KiB`` and ``MiB``. + +``--video-backward-overlap=``, ``--audio-backward-overlap=`` + 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=`` + 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 demux_packet **res) // streams. return -1; } - return ds->eof ? -1 : 0; + return eof ? -1 : 0; } struct demux_packet *pkt = advance_reader_head(ds); @@ -1916,6 +2264,23 @@ static int dequeue_packet(struct demux_stream *ds, struct demux_packet **res) abort(); pkt->next = NULL; + if (ds->in->back_demuxing) { + if (ds->back_range_min) + ds->back_range_min -= 1; + if (ds->back_range_min) { + pkt->back_preroll = true; + } else if (pkt->keyframe) { + // For next backward adjust action. + ds->back_restart_dts = pkt->dts; + ds->back_restart_pos = pkt->pos; + } + if (!ds->back_range_started) { + pkt->back_restart = true; + ds->back_range_started = true; + } + ds->back_seek_pos = MP_PTS_MIN(ds->back_seek_pos, pkt->pts); + } + double ts = PTS_OR_DEF(pkt->dts, pkt->pts); if (ts != MP_NOPTS_VALUE) ds->base_ts = ts; @@ -2544,13 +2909,15 @@ struct demuxer *demux_open_url(const char *url, } // called locked, from user thread only -static void clear_reader_state(struct demux_internal *in) +static void clear_reader_state(struct demux_internal *in, + bool clear_back_state) { for (int n = 0; n < in->num_streams; n++) - ds_clear_reader_state(in->streams[n]->ds); + ds_clear_reader_state(in->streams[n]->ds, clear_back_state); in->warned_queue_overflow = false; in->d_user->filepos = -1; // implicitly synchronized in->blocked = false; + in->need_back_seek = false; assert(in->fw_bytes == 0); } @@ -2561,7 +2928,7 @@ void demux_flush(demuxer_t *demuxer) assert(demuxer == in->d_user); pthread_mutex_lock(&demuxer->in->lock); - clear_reader_state(in); + clear_reader_state(in, true); for (int n = 0; n < in->num_ranges; n++) clear_cached_range(in, in->ranges[n]); free_empty_cached_ranges(in); @@ -2802,12 +3169,20 @@ int demux_seek(demuxer_t *demuxer, double seek_pts, int flags) { struct demux_internal *in = demuxer->in; assert(demuxer == in->d_user); - int res = 0; pthread_mutex_lock(&in->lock); + int res = queue_seek(in, seek_pts, flags, true); + pthread_cond_signal(&in->wakeup); + pthread_mutex_unlock(&in->lock); + return res; +} + +static bool queue_seek(struct demux_internal *in, double seek_pts, int flags, + bool clear_back_state) +{ if (seek_pts == MP_NOPTS_VALUE) - goto done; + return false; MP_VERBOSE(in, "queuing seek to %f%s\n", seek_pts, in->seeking ? " (cascade)" : ""); @@ -2818,26 +3193,35 @@ int demux_seek(demuxer_t *demuxer, double seek_pts, int flags) bool require_cache = flags & SEEK_CACHED; flags &= ~(unsigned)SEEK_CACHED; + bool set_backwards = flags & SEEK_SATAN; + flags &= ~(unsigned)SEEK_SATAN; + + // For HR seeks, the correct seek rounding direction is forward instead of + // backward. + if (set_backwards && (flags & SEEK_HR)) + flags |= SEEK_FORWARD; + struct demux_cached_range *cache_target = find_cache_seek_target(in, seek_pts, flags); if (!cache_target) { if (require_cache) { - MP_VERBOSE(demuxer, "Cached seek not possible.\n"); - goto done; + MP_VERBOSE(in, "Cached seek not possible.\n"); + return false; } - if (!demuxer->seekable) { - MP_WARN(demuxer, "Cannot seek in this file.\n"); - goto done; + if (!in->d_thread->seekable) { + MP_WARN(in, "Cannot seek in this file.\n"); + return false; } } - clear_reader_state(in); + clear_reader_state(in, clear_back_state); in->eof = false; in->last_eof = false; in->idle = true; in->reading = false; + in->back_demuxing = set_backwards; if (cache_target) { execute_cache_seek(in, cache_target, seek_pts, flags); @@ -2855,12 +3239,7 @@ int demux_seek(demuxer_t *demuxer, double seek_pts, int flags) if (!in->threading && in->seeking) execute_seek(in); - res = 1; - -done: - pthread_cond_signal(&in->wakeup); - pthread_mutex_unlock(&in->lock); - return res; + return true; } struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d, diff --git a/demux/demux.h b/demux/demux.h index 085fa26cff..da315adebb 100644 --- a/demux/demux.h +++ b/demux/demux.h @@ -58,6 +58,7 @@ struct demux_reader_state { #define SEEK_FORWARD (1 << 2) // prefer later time if not exact // (if unset, prefer earlier time) #define SEEK_CACHED (1 << 3) // allow packet cache seeks only +#define SEEK_SATAN (1 << 4) // enable backward demuxing #define SEEK_HR (1 << 5) // hr-seek (this is a weak hint only) // Strictness of the demuxer open format check. diff --git a/demux/packet.c b/demux/packet.c index 98ca24d93d..f2291c21bb 100644 --- a/demux/packet.c +++ b/demux/packet.c @@ -129,6 +129,8 @@ void demux_packet_copy_attribs(struct demux_packet *dst, struct demux_packet *sr dst->start = src->start; dst->end = src->end; dst->codec = src->codec; + dst->back_restart = src->back_restart; + dst->back_preroll = src->back_preroll; dst->keyframe = src->keyframe; dst->stream = src->stream; mp_packet_tags_setref(&dst->metadata, src->metadata); diff --git a/demux/packet.h b/demux/packet.h index a5df6ce121..9e28ed52d0 100644 --- a/demux/packet.h +++ b/demux/packet.h @@ -36,6 +36,10 @@ typedef struct demux_packet { bool keyframe; + // backward playback + bool back_restart; // restart point (reverse and return previous frames) + bool back_preroll; // initial discarded frame for smooth decoder reinit + // segmentation (ordered chapters, EDL) bool segmented; struct mp_codec_params *codec; // set to non-NULL iff segmented is set diff --git a/filters/f_decoder_wrapper.c b/filters/f_decoder_wrapper.c index 0edca5c83d..0b2a4f52ec 100644 --- a/filters/f_decoder_wrapper.c +++ b/filters/f_decoder_wrapper.c @@ -87,6 +87,13 @@ struct priv { double start, end; struct demux_packet *new_segment; struct mp_frame packet; + bool packet_fed; + int preroll_discard; + + size_t reverse_queue_byte_size; + struct mp_frame *reverse_queue; + int num_reverse_queue; + bool reverse_queue_complete; struct mp_frame decoded_coverart; int coverart_returned; // 0: no, 1: coverart frame itself, 2: EOF returned @@ -108,11 +115,19 @@ static void reset_decoder(struct priv *p) p->public.pts_reset = false; p->packets_without_output = 0; mp_frame_unref(&p->packet); + p->packet_fed = false; + p->preroll_discard = 0; talloc_free(p->new_segment); p->new_segment = NULL; p->start = p->end = MP_NOPTS_VALUE; p->coverart_returned = 0; + for (int n = 0; n < p->num_reverse_queue; n++) + mp_frame_unref(&p->reverse_queue[n]); + p->num_reverse_queue = 0; + p->reverse_queue_byte_size = 0; + p->reverse_queue_complete = false; + if (p->decoder) mp_filter_reset(p->decoder->f); } @@ -307,18 +322,22 @@ static void process_video_frame(struct priv *p, struct mp_image *mpi) struct MPOpts *opts = p->opt_cache->opts; m_config_cache_update(p->opt_cache); + int dir = p->public.play_dir; + // Note: the PTS is reordered, but the DTS is not. Both should be monotonic. double pts = mpi->pts; double dts = mpi->dts; if (pts != MP_NOPTS_VALUE) { - if (pts < p->codec_pts) + pts *= dir; + if (pts < p->codec_pts && dir > 0) p->num_codec_pts_problems++; p->codec_pts = mpi->pts; } if (dts != MP_NOPTS_VALUE) { - if (dts <= p->codec_dts) + dts *= dir; + if (dts <= p->codec_dts && dir > 0) p->num_codec_dts_problems++; p->codec_dts = mpi->dts; } @@ -401,8 +420,12 @@ void mp_decoder_wrapper_get_video_dec_params(struct mp_decoder_wrapper *d, static void process_audio_frame(struct priv *p, struct mp_aframe *aframe) { + double dir = p->public.play_dir; + double frame_pts = mp_aframe_get_pts(aframe); if (frame_pts != MP_NOPTS_VALUE) { + frame_pts *= dir; + if (p->pts != MP_NOPTS_VALUE) MP_STATS(p, "value %f audio-pts-err", p->pts - frame_pts); @@ -429,7 +452,10 @@ static void process_audio_frame(struct priv *p, struct mp_aframe *aframe) mp_aframe_set_pts(aframe, p->pts); if (p->pts != MP_NOPTS_VALUE) - p->pts += mp_aframe_duration(aframe); + p->pts += mp_aframe_duration(aframe) * dir; + + if (dir < 0) + mp_aframe_set_pts(aframe, p->pts); } @@ -445,8 +471,9 @@ static bool is_new_segment(struct priv *p, struct mp_frame frame) if (frame.type != MP_FRAME_PACKET) return false; struct demux_packet *pkt = frame.data; - return pkt->segmented && (pkt->start != p->start || pkt->end != p->end || - pkt->codec != p->codec); + return (pkt->segmented && (pkt->start != p->start || pkt->end != p->end || + pkt->codec != p->codec)) || + (p->public.play_dir < 0 && pkt->back_restart && p->packet_fed); } static void feed_packet(struct priv *p) @@ -466,6 +493,9 @@ static void feed_packet(struct priv *p) } } + if (!p->packet.type) + return; + // Flush current data if the packet is a new segment. if (is_new_segment(p, p->packet)) { assert(!p->new_segment); @@ -474,7 +504,8 @@ static void feed_packet(struct priv *p) } assert(p->packet.type == MP_FRAME_PACKET || p->packet.type == MP_FRAME_EOF); - struct demux_packet *packet = p->packet.data; + struct demux_packet *packet = + p->packet.type == MP_FRAME_PACKET ? p->packet.data : NULL; // For video framedropping, including parts of the hr-seek logic. if (p->decoder->control) { @@ -488,7 +519,7 @@ static void feed_packet(struct priv *p) if (p->public.attempt_framedrops) framedrop_type = 1; - if (start_pts != MP_NOPTS_VALUE && packet && + if (start_pts != MP_NOPTS_VALUE && packet && p->public.play_dir > 0 && packet->pts < start_pts - .005 && !p->has_broken_packet_pts) framedrop_type = 2; @@ -511,8 +542,12 @@ static void feed_packet(struct priv *p) if (p->first_packet_pdts == MP_NOPTS_VALUE) p->first_packet_pdts = pkt_pdts; + if (packet && packet->back_preroll) + p->preroll_discard += 1; + mp_pin_in_write(p->decoder->f->pins[0], p->packet); p->packet = MP_NO_FRAME; + p->packet_fed = true; p->packets_without_output += 1; } @@ -549,6 +584,10 @@ static bool process_decoded_frame(struct priv *p, struct mp_frame *frame) double pts = mp_aframe_get_pts(aframe); if (pts != MP_NOPTS_VALUE && p->start != MP_NOPTS_VALUE) segment_ended = pts >= p->end; + + if (p->public.play_dir < 0 && !mp_aframe_reverse(aframe)) + MP_ERR(p, "Couldn't reverse audio frame.\n"); + if (mp_aframe_get_size(aframe) == 0) mp_frame_unref(frame); } else { @@ -558,6 +597,35 @@ static bool process_decoded_frame(struct priv *p, struct mp_frame *frame) return segment_ended; } +static void enqueue_backward_frame(struct priv *p, struct mp_frame frame) +{ + bool eof = frame.type == MP_FRAME_EOF; + + if (!eof) { + struct MPOpts *opts = p->opt_cache->opts; + + uint64_t queue_size = 0; + switch (p->header->type) { + case STREAM_VIDEO: queue_size = opts->video_reverse_size; break; + case STREAM_AUDIO: queue_size = opts->audio_reverse_size; break; + } + + if (p->reverse_queue_byte_size >= queue_size) { + MP_ERR(p, "Reversal queue overflow, discarding frame.\n"); + mp_frame_unref(&frame); + return; + } + + p->reverse_queue_byte_size += mp_frame_approx_size(frame); + } + + // Note: EOF (really BOF) is propagated, but not reversed. + MP_TARRAY_INSERT_AT(p, p->reverse_queue, p->num_reverse_queue, + eof ? 0 : p->num_reverse_queue, frame); + + p->reverse_queue_complete = eof; +} + static void read_frame(struct priv *p) { struct mp_pin *pin = p->f->ppins[0]; @@ -576,6 +644,14 @@ static void read_frame(struct priv *p) return; } + if (p->reverse_queue_complete && p->num_reverse_queue) { + struct mp_frame frame = p->reverse_queue[p->num_reverse_queue - 1]; + p->num_reverse_queue -= 1; + mp_pin_in_write(pin, frame); + return; + } + p->reverse_queue_complete = false; + struct mp_frame frame = mp_pin_out_read(p->decoder->f->pins[1]); if (!frame.type) return; @@ -593,23 +669,47 @@ static void read_frame(struct priv *p) } p->packets_without_output = 0; + if (p->preroll_discard > 0 && frame.type != MP_FRAME_EOF) { + p->preroll_discard -= 1; + mp_frame_unref(&frame); + mp_filter_internal_mark_progress(p->f); + return; + } + bool segment_ended = process_decoded_frame(p, &frame); + if (p->public.play_dir < 0 && frame.type) { + enqueue_backward_frame(p, frame); + frame = MP_NO_FRAME; + } + // If there's a new segment, start it as soon as we're drained/finished. if (segment_ended && p->new_segment) { struct demux_packet *new_segment = p->new_segment; p->new_segment = NULL; + struct mp_frame *reverse_queue = p->reverse_queue; + int num_reverse_queue = p->num_reverse_queue; + p->reverse_queue = NULL; + p->num_reverse_queue = 0; + reset_decoder(p); - if (p->codec != new_segment->codec) { - p->codec = new_segment->codec; - if (!mp_decoder_wrapper_reinit(&p->public)) - mp_filter_internal_mark_failed(p->f); + if (new_segment->segmented) { + if (p->codec != new_segment->codec) { + p->codec = new_segment->codec; + if (!mp_decoder_wrapper_reinit(&p->public)) + mp_filter_internal_mark_failed(p->f); + } + + p->start = new_segment->start; + p->end = new_segment->end; } - p->start = new_segment->start; - p->end = new_segment->end; + assert(!p->reverse_queue); + p->reverse_queue = reverse_queue; + p->num_reverse_queue = num_reverse_queue; + p->reverse_queue_complete = p->num_reverse_queue > 0; p->packet = MAKE_FRAME(MP_FRAME_PACKET, new_segment); mp_filter_internal_mark_progress(p->f); @@ -655,6 +755,8 @@ struct mp_decoder_wrapper *mp_decoder_wrapper_create(struct mp_filter *parent, p->codec = p->header->codec; w->f = f; + w->play_dir = 1; + struct MPOpts *opts = p->opt_cache->opts; mp_filter_add_pin(f, MP_PIN_OUT, "out"); diff --git a/filters/f_decoder_wrapper.h b/filters/f_decoder_wrapper.h index 119e0f9eb6..b69c0c7680 100644 --- a/filters/f_decoder_wrapper.h +++ b/filters/f_decoder_wrapper.h @@ -37,6 +37,7 @@ struct mp_decoder_wrapper { // Can be set by user. struct mp_recorder_sink *recorder_sink; + int play_dir; // --- for STREAM_VIDEO diff --git a/filters/frame.c b/filters/frame.c index f1d4c98eab..200e900fa2 100644 --- a/filters/frame.c +++ b/filters/frame.c @@ -14,6 +14,7 @@ struct frame_handler { void *(*new_ref)(void *data); double (*get_pts)(void *data); void (*set_pts)(void *data, double pts); + int (*approx_size)(void *data); AVFrame *(*new_av_ref)(void *data); void *(*from_av_ref)(AVFrame *data); void (*free)(void *data); @@ -34,6 +35,11 @@ static void video_set_pts(void *data, double pts) ((struct mp_image *)data)->pts = pts; } +static int video_approx_size(void *data) +{ + return mp_image_approx_byte_size(data); +} + static AVFrame *video_new_av_ref(void *data) { return mp_image_to_av_frame(data); @@ -59,6 +65,11 @@ static void audio_set_pts(void *data, double pts) mp_aframe_set_pts(data, pts); } +static int audio_approx_size(void *data) +{ + return mp_aframe_approx_byte_size(data); +} + static AVFrame *audio_new_av_ref(void *data) { return mp_aframe_to_avframe(data); @@ -88,6 +99,7 @@ static const struct frame_handler frame_handlers[] = { .new_ref = video_ref, .get_pts = video_get_pts, .set_pts = video_set_pts, + .approx_size = video_approx_size, .new_av_ref = video_new_av_ref, .from_av_ref = video_from_av_ref, .free = talloc_free, @@ -98,6 +110,7 @@ static const struct frame_handler frame_handlers[] = { .new_ref = audio_ref, .get_pts = audio_get_pts, .set_pts = audio_set_pts, + .approx_size = audio_approx_size, .new_av_ref = audio_new_av_ref, .from_av_ref = audio_from_av_ref, .free = talloc_free, @@ -160,6 +173,13 @@ void mp_frame_set_pts(struct mp_frame frame, double pts) frame_handlers[frame.type].set_pts(frame.data, pts); } +int mp_frame_approx_size(struct mp_frame frame) +{ + if (frame_handlers[frame.type].approx_size) + return frame_handlers[frame.type].approx_size(frame.data); + return 0; +} + AVFrame *mp_frame_to_av(struct mp_frame frame, struct AVRational *tb) { if (!frame_handlers[frame.type].new_av_ref) diff --git a/filters/frame.h b/filters/frame.h index 606ca38846..4c6c4ef127 100644 --- a/filters/frame.h +++ b/filters/frame.h @@ -45,6 +45,9 @@ struct mp_frame mp_frame_ref(struct mp_frame frame); double mp_frame_get_pts(struct mp_frame frame); void mp_frame_set_pts(struct mp_frame frame, double pts); +// Estimation of total size in bytes. This is for buffering purposes. +int mp_frame_approx_size(struct mp_frame frame); + struct AVFrame; struct AVRational; struct AVFrame *mp_frame_to_av(struct mp_frame frame, struct AVRational *tb); diff --git a/options/options.c b/options/options.c index 2c2fcc941d..65c6d7234e 100644 --- a/options/options.c +++ b/options/options.c @@ -405,6 +405,11 @@ const m_option_t mp_opts[] = { OPT_REL_TIME("end", play_end, 0), OPT_REL_TIME("length", play_length, 0), + OPT_CHOICE("play-direction", play_dir, 0, + ({"forward", 1}, {"backward", -1})), + OPT_BYTE_SIZE("video-reversal-buffer", video_reverse_size, 0, 0, (size_t)-1), + OPT_BYTE_SIZE("audio-reversal-buffer", audio_reverse_size, 0, 0, (size_t)-1), + OPT_FLAG("rebase-start-time", rebase_start_time, 0), OPT_TIME("ab-loop-a", ab_loop[0], 0, .min = MP_NOPTS_VALUE), @@ -937,6 +942,9 @@ const struct MPOpts mp_default_opts = { .audiofile_auto = -1, .osd_bar_visible = 1, .screenshot_template = "mpv-shot%n", + .play_dir = 1, + .video_reverse_size = 1 * 1024 * 1024 * 1024, + .audio_reverse_size = 64 * 1024 * 1024, .audio_output_channels = { .set = 1, diff --git a/options/options.h b/options/options.h index 5f7c560e71..26693221a3 100644 --- a/options/options.h +++ b/options/options.h @@ -224,6 +224,7 @@ typedef struct MPOpts { struct m_rel_time play_start; struct m_rel_time play_end; struct m_rel_time play_length; + int play_dir; int rebase_start_time; int play_frames; double ab_loop[2]; @@ -252,6 +253,8 @@ typedef struct MPOpts { int prefetch_open; char *audio_demuxer_name; char *sub_demuxer_name; + int64_t video_reverse_size; + int64_t audio_reverse_size; int cache_pause; int cache_pause_initial; diff --git a/player/command.c b/player/command.c index 6682398b8b..68ce861c6f 100644 --- a/player/command.c +++ b/player/command.c @@ -465,6 +465,19 @@ static int mp_property_playback_speed(void *ctx, struct m_property *prop, return mp_property_generic_option(mpctx, prop, action, arg); } +static int mp_property_play_direction(void *ctx, struct m_property *prop, + int action, void *arg) +{ + MPContext *mpctx = ctx; + if (action == M_PROPERTY_SET) { + if (mpctx->play_dir != *(int *)arg) { + queue_seek(mpctx, MPSEEK_ABSOLUTE, get_current_time(mpctx), + MPSEEK_EXACT, 0); + } + } + return mp_property_generic_option(mpctx, prop, action, arg); +} + static int mp_property_av_speed_correction(void *ctx, struct m_property *prop, int action, void *arg) { @@ -3561,6 +3574,8 @@ static const struct m_property mp_properties_base[] = { {"property-list", mp_property_list}, {"profile-list", mp_profile_list}, + {"play-direction", mp_property_play_direction}, + M_PROPERTY_ALIAS("video", "vid"), M_PROPERTY_ALIAS("audio", "aid"), M_PROPERTY_ALIAS("sub", "sid"), diff --git a/player/core.h b/player/core.h index f0a66ffdff..d2183a7537 100644 --- a/player/core.h +++ b/player/core.h @@ -326,6 +326,7 @@ typedef struct MPContext { enum playback_status video_status, audio_status; bool restart_complete; + int play_dir; // Factors to multiply with opts->playback_speed to get the total audio or // video speed (usually 1.0, but can be set to by the sync code). double speed_factor_v, speed_factor_a; diff --git a/player/loadfile.c b/player/loadfile.c index f4fad8a914..c16b8e8152 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -1551,6 +1551,11 @@ static void play_current_file(struct MPContext *mpctx) } double play_start_pts = get_play_start_pts(mpctx); + + // Backward playback -> start from end by default. + if (play_start_pts == MP_NOPTS_VALUE && opts->play_dir < 0) + play_start_pts = MPMAX(mpctx->demuxer->duration, 0); + if (play_start_pts != MP_NOPTS_VALUE) { /* * get_play_start_pts returns rebased values, but diff --git a/player/main.c b/player/main.c index 0a11bcf7d2..e1f3285984 100644 --- a/player/main.c +++ b/player/main.c @@ -280,6 +280,7 @@ struct MPContext *mp_create(void) .playback_abort = mp_cancel_new(mpctx), .thread_pool = mp_thread_pool_create(mpctx, 0, 1, 30), .stop_play = PT_STOP, + .play_dir = 1, }; pthread_mutex_init(&mpctx->abort_lock, NULL); diff --git a/player/playloop.c b/player/playloop.c index 61ade84755..85890c2ef1 100644 --- a/player/playloop.c +++ b/player/playloop.c @@ -42,6 +42,7 @@ #include "audio/out/ao.h" #include "demux/demux.h" #include "stream/stream.h" +#include "sub/dec_sub.h" #include "sub/osd.h" #include "video/out/vo.h" @@ -223,6 +224,14 @@ void reset_playback_state(struct MPContext *mpctx) reset_audio_state(mpctx); reset_subtitle_state(mpctx); + for (int n = 0; n < mpctx->num_tracks; n++) { + struct track *t = mpctx->tracks[n]; + if (t->dec) + t->dec->play_dir = mpctx->play_dir; + if (t->d_sub) + sub_set_play_dir(t->d_sub, mpctx->play_dir); + } + mpctx->hrseek_active = false; mpctx->hrseek_lastframe = false; mpctx->hrseek_backstep = false; @@ -317,6 +326,10 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek) if (!mpctx->demuxer->seekable) demux_flags |= SEEK_CACHED; + int play_dir = opts->play_dir; + if (play_dir < 0) + demux_flags |= SEEK_SATAN; + if (!demux_seek(mpctx->demuxer, demux_pts, demux_flags)) { if (!mpctx->demuxer->seekable) { MP_ERR(mpctx, "Cannot seek in this stream.\n"); @@ -325,6 +338,8 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek) return; } + mpctx->play_dir = play_dir; + // Seek external, extra files too: bool has_video = false; struct track *external_audio = NULL; @@ -336,7 +351,7 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek) main_new_pos += get_track_seek_offset(mpctx, track); if (demux_flags & SEEK_FACTOR) main_new_pos = seek_pts; - demux_seek(track->demuxer, main_new_pos, 0); + demux_seek(track->demuxer, main_new_pos, demux_flags & SEEK_SATAN); if (track->type == STREAM_AUDIO && !external_audio) external_audio = track; } @@ -357,7 +372,9 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek) // granularity is coarser than audio). The result would be playing video with // silence until the audio seek target is reached. Work around by blocking // the demuxer (decoders can't read) and seeking to video position later. - if (has_video && external_audio && !hr_seek && !(demux_flags & SEEK_FORWARD)) { + if (has_video && external_audio && !hr_seek && mpctx->play_dir > 0 && + !(demux_flags & SEEK_FORWARD)) + { MP_VERBOSE(mpctx, "delayed seek for aid=%d\n", external_audio->user_tid); demux_block_reading(external_audio->demuxer, true); mpctx->seek_slave = external_audio; @@ -370,7 +387,7 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek) if (hr_seek) { mpctx->hrseek_active = true; mpctx->hrseek_backstep = seek.type == MPSEEK_BACKSTEP; - mpctx->hrseek_pts = seek_pts; + mpctx->hrseek_pts = seek_pts * mpctx->play_dir; // allow decoder to drop frames before hrseek_pts bool hrseek_framedrop = !hr_seek_very_exact && opts->hr_seek_framedrop; @@ -472,7 +489,7 @@ double get_current_time(struct MPContext *mpctx) struct demuxer *demuxer = mpctx->demuxer; if (demuxer) { if (mpctx->playback_pts != MP_NOPTS_VALUE) - return mpctx->playback_pts; + return mpctx->playback_pts * mpctx->play_dir; if (mpctx->last_seek_pts != MP_NOPTS_VALUE) return mpctx->last_seek_pts; } @@ -630,7 +647,7 @@ static void handle_update_cache(struct MPContext *mpctx) int cache_buffer = 100; bool use_pause_on_low_cache = demux_is_network_cached(mpctx->demuxer) && - opts->cache_pause; + opts->cache_pause && mpctx->play_dir > 0; if (!mpctx->restart_complete) { // Audio or video is restarting, and initial buffering is enabled. Make diff --git a/sub/dec_sub.c b/sub/dec_sub.c index ae6a064be2..6a0a753076 100644 --- a/sub/dec_sub.c +++ b/sub/dec_sub.c @@ -58,6 +58,7 @@ struct dec_sub { struct attachment_list *attachments; struct sh_stream *sh; + int play_dir; double last_pkt_pts; bool preload_attempted; double video_fps; @@ -97,7 +98,7 @@ static double pts_to_subtitle(struct dec_sub *sub, double pts) struct mp_subtitle_opts *opts = sub->opts; if (pts != MP_NOPTS_VALUE) - pts = (pts - opts->sub_delay) / sub->sub_speed; + pts = (pts * sub->play_dir - opts->sub_delay) / sub->sub_speed; return pts; } @@ -107,7 +108,7 @@ static double pts_from_subtitle(struct dec_sub *sub, double pts) struct mp_subtitle_opts *opts = sub->opts; if (pts != MP_NOPTS_VALUE) - pts = pts * sub->sub_speed + opts->sub_delay; + pts = (pts * sub->sub_speed + opts->sub_delay) * sub->play_dir; return pts; } @@ -186,6 +187,7 @@ struct dec_sub *sub_create(struct mpv_global *global, struct sh_stream *sh, .sh = sh, .codec = sh->codec, .attachments = talloc_steal(sub, attachments), + .play_dir = 1, .last_pkt_pts = MP_NOPTS_VALUE, .last_vo_pts = MP_NOPTS_VALUE, .start = MP_NOPTS_VALUE, @@ -433,3 +435,9 @@ void sub_set_recorder_sink(struct dec_sub *sub, struct mp_recorder_sink *sink) pthread_mutex_unlock(&sub->lock); } +void sub_set_play_dir(struct dec_sub *sub, int dir) +{ + pthread_mutex_lock(&sub->lock); + sub->play_dir = dir; + pthread_mutex_unlock(&sub->lock); +} diff --git a/sub/dec_sub.h b/sub/dec_sub.h index 3303cc9a5c..06d4a6127e 100644 --- a/sub/dec_sub.h +++ b/sub/dec_sub.h @@ -42,6 +42,7 @@ void sub_reset(struct dec_sub *sub); void sub_select(struct dec_sub *sub, bool selected); void sub_update_opts(struct dec_sub *sub); void sub_set_recorder_sink(struct dec_sub *sub, struct mp_recorder_sink *sink); +void sub_set_play_dir(struct dec_sub *sub, int dir); int sub_control(struct dec_sub *sub, enum sd_ctrl cmd, void *arg); diff --git a/video/mp_image.c b/video/mp_image.c index b5780b08a0..f846b0d3d3 100644 --- a/video/mp_image.c +++ b/video/mp_image.c @@ -265,6 +265,19 @@ struct mp_image *mp_image_alloc(int imgfmt, int w, int h) return mpi; } +int mp_image_approx_byte_size(struct mp_image *img) +{ + int total = sizeof(*img); + + for (int n = 0; n < MP_MAX_PLANES; n++) { + struct AVBufferRef *buf = img->bufs[n]; + if (buf) + total += buf->size; + } + + return total; +} + struct mp_image *mp_image_new_copy(struct mp_image *img) { struct mp_image *new = mp_image_alloc(img->imgfmt, img->w, img->h); diff --git a/video/mp_image.h b/video/mp_image.h index d321a27285..4727ed6f8d 100644 --- a/video/mp_image.h +++ b/video/mp_image.h @@ -163,6 +163,8 @@ void mp_image_setfmt(mp_image_t* mpi, int out_fmt); void mp_image_steal_data(struct mp_image *dst, struct mp_image *src); void mp_image_unref_data(struct mp_image *img); +int mp_image_approx_byte_size(struct mp_image *img); + struct mp_image *mp_image_new_dummy_ref(struct mp_image *img); struct mp_image *mp_image_new_custom_ref(struct mp_image *img, void *arg, void (*free)(void *arg));