summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2019-05-24 01:47:10 +0200
committerwm4 <wm4@nowhere>2019-09-19 20:37:04 +0200
commita3ac2019eddbfa7d763c6da808c1cf0ebda18c53 (patch)
tree57ff89e29a008048448fd50fa20d4baad13f4c04
parent27c5550de2693d27ed24d3838a4ae16a06a9dd5d (diff)
downloadmpv-a3ac2019eddbfa7d763c6da808c1cf0ebda18c53.tar.bz2
mpv-a3ac2019eddbfa7d763c6da808c1cf0ebda18c53.tar.xz
demux: fix initial backward demuxing state in some cases
Just "mpv file.mkv --play-direction=backward" did not work, because backward demuxing from the very end was not implemented. This is another corner case, because the resume mechanism so far requires a packet "position" (dts or pos) as reference. Now "EOF" is another possible reference. Also, the backstep mechanism could cause streams to find different playback start positions, basically leading to random playback start (instead of what you specified with --start). This happens only if backstep seeks are involved (i.e. no cached data yet), but since this is usually the case at playback start, it always happened. It was racy too, because it depended on the order the decoders on other threads requested new data. The comment below "resume_earlier" has some more blabla. Some other details are changed. I'm giving up on the "from_cache" parameter, and don't try to detect the situation when the demuxer does not seek properly. Instead, always seek back, hopefully some more. Instead of trying to adjust the backstep seek target by a random value of 1.0 seconds. Instead, always rely on the random value provided by the user via --demuxer-backward-playback-step. If the demuxer should really get "stuck" and somehow miss the seek target badly, or the user sets the option value to 0, then the demuxer will not make any progress and just eat CPU. (Although due to backward seek semantics used for backstep seeks, even a very small seek step size will work. Just not 0.) It seems this also fixes backstepping correctly when the initial seek ended at the last keyframe range. (The explanation above was about the case when it ends at EOF. These two cases are different. In the former, you just need to step to the previous keyframe range, which was broken because it didn't always react correctly to reaching EOF. In the latter, you need to do a separate search for the last keyframe.)
-rw-r--r--DOCS/man/options.rst8
-rw-r--r--demux/demux.c154
2 files changed, 95 insertions, 67 deletions
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 9cfc5b0624..99bb8cad8d 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -489,14 +489,6 @@ Playback Control
A workaround is to remux to a format like mkv, which enforces packet
boundaries. Making mpv cache the entire file in memory also works.
- - Starting from the very end of the file often does not work. This includes
- starting playback of a file while backward playback mode is enabled, and
- no initial seek is set with ``--start``. You need to use something like
- ``--start=99%`` to try to get the end.
-
- - Starting backward playback at a specific time position without pre-caching
- the entire file usually misses the seek target due to a race condition.
-
Tuning:
- Remove all ``--vf``/``--af`` filters you have set. Disable deinterlacing.
diff --git a/demux/demux.c b/demux/demux.c
index 967b0b176f..6711920811 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -348,6 +348,7 @@ struct demux_stream {
// back_range_started or back_restarting are set.
int64_t back_restart_pos;
double back_restart_dts;
+ bool back_restart_eof; // restart position is at EOF; overrides pos/dts
bool back_restarting; // searching keyframe before restart pos
// Current PTS lower bound for back demuxing.
double back_seek_pos;
@@ -754,6 +755,7 @@ static void ds_clear_reader_state(struct demux_stream *ds,
if (clear_back_state) {
ds->back_restart_pos = -1;
ds->back_restart_dts = MP_NOPTS_VALUE;
+ ds->back_restart_eof = false;
ds->back_restarting = false;
ds->back_seek_pos = MP_NOPTS_VALUE;
ds->back_resume_pos = -1;
@@ -1257,76 +1259,79 @@ static void perform_backward_seek(struct demux_internal *in)
target -= in->opts->back_seek_size;
MP_VERBOSE(in, "triggering backward seek to get more packets\n");
+
+ // Note: we don't want it to use SEEK_FORWARD, while the player frontend
+ // wants it. As a fragile hack, SEEK_HR controls this.
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)
+static void find_backward_restart_pos(struct demux_stream *ds)
{
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;
- }
+ if (first && !first->keyframe)
+ MP_WARN(in, "Queue not starting on keyframe.\n");
// Packet at back_restart_pos. (Note: we don't actually need it, only the
// packet immediately before it. But same effort.)
+ // If this is NULL, look for EOF (resume from very last keyframe).
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 (ds->back_restart_eof) {
+ // We're trying to find EOF (without discarding packets). Only continue
+ // if we really reach EOF.
+ if (!ds->eof)
+ return;
+ } else if (!first && ds->eof) {
+ // Reached EOF during normal backward demuxing. We probably returned the
+ // last keyframe range to user. Need to resume at an earlier position.
+ // Fall through, hit the no-keyframe case (and possible the BOF check
+ // if there are no packets at all), and then resume_earlier.
+ } else if (!first) {
+ return; // no packets yet
+ } else {
+ 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))
+ goto resume_earlier; // current position is too late; seek back
+
+
+ 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 (!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.
@@ -1397,13 +1402,25 @@ static void find_backward_restart_pos(struct demux_stream *ds, bool from_cache)
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;
+ // We want to seek back to get earlier packets. But before we do this, we
+ // must be sure that other streams have initialized their state. The only
+ // time when this state is not initialized is right after the seek that
+ // started backward demuxing (not any subsequent backstep seek). If this
+ // initialization is omitted, the stream would try to start demuxing from
+ // the "current" position. If another stream backstepped before that, the
+ // other stream will miss the original seek target, and start playback from
+ // a position that is too early.
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds2 = in->streams[n]->ds;
+ if (ds2 == ds || !ds2->eager)
+ continue;
+
+ if (!ds2->reader_head && !ds2->back_resuming && !ds2->back_restarting) {
+ MP_VERBOSE(in, "delaying stream %d for %d\n", ds->index, ds2->index);
+ return;
+ }
+ }
+
in->need_back_seek = true;
}
@@ -1436,7 +1453,7 @@ static void back_demux_see_packets(struct demux_stream *ds)
}
if (ds->back_restarting)
- find_backward_restart_pos(ds, false);
+ find_backward_restart_pos(ds);
}
// Resume demuxing from an earlier position for backward playback. May trigger
@@ -1450,6 +1467,14 @@ static void step_backwards(struct demux_stream *ds)
assert(!ds->back_restarting);
ds->back_restarting = true;
+ // No valid restart pos, but EOF reached -> find last restart pos before EOF.
+ ds->back_restart_eof = ds->back_restart_dts == MP_NOPTS_VALUE &&
+ ds->back_restart_pos < 0 &&
+ ds->eof;
+
+ if (ds->back_restart_eof)
+ MP_VERBOSE(in, "backward eof on stream %d\n", ds->index);
+
// 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
@@ -1466,7 +1491,7 @@ static void step_backwards(struct demux_stream *ds)
while (ds->reader_head && !ds->reader_head->keyframe)
advance_reader_head(ds);
- find_backward_restart_pos(ds, true);
+ find_backward_restart_pos(ds);
}
// Add the keyframe to the end of the index. Not all packets are actually added.
@@ -1934,6 +1959,7 @@ static bool read_packet(struct demux_internal *in)
if (!ds->eof && eof) {
ds->eof = true;
adjust_seek_range_on_packet(ds, NULL);
+ back_demux_see_packets(ds);
wakeup_ds(ds);
}
}
@@ -1977,6 +2003,7 @@ static bool read_packet(struct demux_internal *in)
if (!ds->eof) {
ds->eof = true;
adjust_seek_range_on_packet(ds, NULL);
+ back_demux_see_packets(ds);
wakeup_ds(ds);
}
}
@@ -2235,8 +2262,10 @@ static int dequeue_packet(struct demux_stream *ds, struct demux_packet **res)
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))
+ if ((ds->back_range_started && !ds->back_range_min &&
+ ((ds->reader_head && ds->reader_head->keyframe) || eof)) ||
+ (!ds->back_range_started && !ds->back_range_min &&
+ !ds->reader_head && eof))
{
step_backwards(ds);
if (ds->back_restarting)
@@ -2276,6 +2305,7 @@ static int dequeue_packet(struct demux_stream *ds, struct demux_packet **res)
// For next backward adjust action.
ds->back_restart_dts = pkt->dts;
ds->back_restart_pos = pkt->pos;
+ ds->back_restart_eof = false;
}
if (!ds->back_range_started) {
pkt->back_restart = true;
@@ -3203,8 +3233,8 @@ static bool queue_seek(struct demux_internal *in, double seek_pts, int flags,
bool set_backwards = flags & SEEK_SATAN;
flags &= ~(unsigned)SEEK_SATAN;
- // For HR seeks, the correct seek rounding direction is forward instead of
- // backward.
+ // For HR seeks in backward playback mode, the correct seek rounding
+ // direction is forward instead of backward.
if (set_backwards && (flags & SEEK_HR))
flags |= SEEK_FORWARD;
@@ -3240,8 +3270,14 @@ static bool queue_seek(struct demux_internal *in, double seek_pts, int flags,
in->seek_pts = seek_pts;
}
- for (int n = 0; n < in->num_streams; n++)
- wakeup_ds(in->streams[n]->ds);
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+
+ if (in->back_demuxing && clear_back_state)
+ ds->back_seek_pos = seek_pts;
+
+ wakeup_ds(ds);
+ }
if (!in->threading && in->seeking)
execute_seek(in);