summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/man/options.rst5
-rw-r--r--options/options.c4
-rw-r--r--player/audio.c3
-rw-r--r--player/core.h2
-rw-r--r--player/misc.c13
-rw-r--r--player/osd.c2
-rw-r--r--player/playloop.c32
-rw-r--r--player/video.c62
8 files changed, 74 insertions, 49 deletions
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 74a623e6c5..59cd66ccbc 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -222,7 +222,7 @@ Playback Control
chapter instead. A negative value means always go back to the previous
chapter.
-``--hr-seek=<no|absolute|yes>``
+``--hr-seek=<no|absolute|yes|default>``
Select when to use precise seeks that are not limited to keyframes. Such
seeks require decoding video from the previous keyframe up to the target
position and so can take some time depending on decoding performance. For
@@ -234,6 +234,9 @@ Playback Control
:absolute: Use precise seeks if the seek is to an absolute position in the
file, such as a chapter seek, but not for relative seeks like
the default behavior of arrow keys (default).
+ :default: Like ``absolute``, but enable hr-seeks in audio-only cases. The
+ exact behavior is implementation specific and may change with
+ new releases.
:yes: Use precise seeks whenever possible.
:always: Same as ``yes`` (for compatibility).
diff --git a/options/options.c b/options/options.c
index 3dc85162fd..a164c6a97c 100644
--- a/options/options.c
+++ b/options/options.c
@@ -689,7 +689,8 @@ static const m_option_t mp_opts[] = {
OPT_DOUBLE("video-sync-adrop-size", sync_audio_drop_size,
M_OPT_MIN | M_OPT_MAX, .min = 0, .max = 1),
OPT_CHOICE("hr-seek", hr_seek, 0,
- ({"no", -1}, {"absolute", 0}, {"yes", 1}, {"always", 1})),
+ ({"no", -1}, {"absolute", 0}, {"yes", 1}, {"always", 1},
+ {"default", 2})),
OPT_FLOAT("hr-seek-demuxer-offset", hr_seek_demuxer_offset, 0),
OPT_FLAG("hr-seek-framedrop", hr_seek_framedrop, 0),
OPT_CHOICE_OR_INT("autosync", autosync, 0, 0, 10000,
@@ -947,6 +948,7 @@ static const struct MPOpts mp_default_opts = {
.ordered_chapters = 1,
.chapter_merge_threshold = 100,
.chapter_seek_threshold = 5.0,
+ .hr_seek = 2,
.hr_seek_framedrop = 1,
.sync_max_video_change = 1,
.sync_max_audio_change = 0.125,
diff --git a/player/audio.c b/player/audio.c
index aae1198835..8aba3d860b 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -625,7 +625,8 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
!mp_audio_buffer_samples(mpctx->ao_chain->ao_buffer))
return false; // no audio read yet
- bool sync_to_video = mpctx->vo_chain && mpctx->video_status != STATUS_EOF;
+ bool sync_to_video = mpctx->vo_chain && mpctx->video_status != STATUS_EOF &&
+ !mpctx->vo_chain->is_sparse;
double sync_pts = MP_NOPTS_VALUE;
if (sync_to_video) {
diff --git a/player/core.h b/player/core.h
index 865b2e9f53..a6606af91c 100644
--- a/player/core.h
+++ b/player/core.h
@@ -230,6 +230,8 @@ enum playback_status {
STATUS_EOF, // playback has ended, or is disabled
};
+const char *mp_status_str(enum playback_status st);
+
#define NUM_PTRACKS 2
typedef struct MPContext {
diff --git a/player/misc.c b/player/misc.c
index f6a63ec7ef..4246c1c6f9 100644
--- a/player/misc.c
+++ b/player/misc.c
@@ -301,3 +301,16 @@ void merge_playlist_files(struct playlist *pl)
playlist_add_file(pl, edl);
talloc_free(edl);
}
+
+const char *mp_status_str(enum playback_status st)
+{
+ switch (st) {
+ case STATUS_SYNCING: return "syncing";
+ case STATUS_FILLING: return "filling";
+ case STATUS_READY: return "ready";
+ case STATUS_PLAYING: return "playing";
+ case STATUS_DRAINING: return "draining";
+ case STATUS_EOF: return "eof";
+ default: return "bug";
+ }
+}
diff --git a/player/osd.c b/player/osd.c
index 93d6302cc0..ff9bd9b429 100644
--- a/player/osd.c
+++ b/player/osd.c
@@ -192,7 +192,7 @@ static char *get_term_status_msg(struct MPContext *mpctx)
saddf(&line, " x%4.2f", opts->playback_speed);
// A-V sync
- if (mpctx->ao_chain && mpctx->vo_chain && !mpctx->vo_chain->is_coverart) {
+ if (mpctx->ao_chain && mpctx->vo_chain && !mpctx->vo_chain->is_sparse) {
saddf(&line, " A-V:%7.3f", mpctx->last_av_difference);
if (fabs(mpctx->total_avsync_change) > 0.05)
saddf(&line, " ct:%7.3f", mpctx->total_avsync_change);
diff --git a/player/playloop.c b/player/playloop.c
index 6c390a76e2..597bcd3981 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -290,10 +290,11 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
double demux_pts = seek_pts;
- bool hr_seek = opts->correct_pts && seek.exact != MPSEEK_KEYFRAME &&
- ((opts->hr_seek == 0 && seek.type == MPSEEK_ABSOLUTE) ||
- opts->hr_seek > 0 || seek.exact >= MPSEEK_EXACT) &&
- seek_pts != MP_NOPTS_VALUE;
+ bool hr_seek = (opts->correct_pts && seek.exact != MPSEEK_KEYFRAME &&
+ seek_pts != MP_NOPTS_VALUE) &&
+ (seek.exact >= MPSEEK_EXACT || opts->hr_seek == 1 ||
+ (opts->hr_seek >= 0 && seek.type == MPSEEK_ABSOLUTE) ||
+ (opts->hr_seek == 2 && (!mpctx->vo_chain || mpctx->vo_chain->is_sparse)));
if (seek.type == MPSEEK_FACTOR || seek.amount < 0 ||
(seek.type == MPSEEK_ABSOLUTE && seek.amount < mpctx->last_chapter_pts))
@@ -661,7 +662,7 @@ static void handle_osd_redraw(struct MPContext *mpctx)
return;
}
// Don't redraw immediately during a seek (makes it significantly slower).
- bool use_video = mpctx->vo_chain && !mpctx->vo_chain->is_coverart;
+ bool use_video = mpctx->vo_chain && !mpctx->vo_chain->is_sparse;
if (use_video && mp_time_sec() - mpctx->start_timestamp < 0.1) {
mp_set_timeout(mpctx, 0.1);
return;
@@ -1110,13 +1111,6 @@ static void handle_playback_restart(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
- // Do not wait for video stream if it only has sparse frames.
- if (mpctx->vo_chain && mpctx->vo_chain->is_sparse &&
- mpctx->video_status < STATUS_READY)
- {
- mpctx->video_status = STATUS_READY;
- }
-
if (mpctx->audio_status < STATUS_READY ||
mpctx->video_status < STATUS_READY)
return;
@@ -1127,6 +1121,7 @@ static void handle_playback_restart(struct MPContext *mpctx)
mpctx->video_status = STATUS_PLAYING;
get_relative_time(mpctx);
mp_wakeup_core(mpctx);
+ MP_DBG(mpctx, "starting video playback\n");
}
if (mpctx->audio_status == STATUS_READY) {
@@ -1139,14 +1134,7 @@ static void handle_playback_restart(struct MPContext *mpctx)
return;
}
- // Video needed, but not started yet -> wait.
- if (mpctx->vo_chain &&
- !mpctx->vo_chain->is_coverart &&
- !mpctx->vo_chain->is_sparse &&
- mpctx->video_status <= STATUS_READY)
- return;
-
- MP_VERBOSE(mpctx, "starting audio playback\n");
+ MP_DBG(mpctx, "starting audio playback\n");
mpctx->audio_status = STATUS_PLAYING;
fill_audio_out_buffers(mpctx); // actually play prepared buffer
mp_wakeup_core(mpctx);
@@ -1178,7 +1166,9 @@ static void handle_playback_restart(struct MPContext *mpctx)
mpctx->playing_msg_shown = true;
mp_wakeup_core(mpctx);
update_ab_loop_clip(mpctx);
- MP_VERBOSE(mpctx, "playback restart complete @ %f\n", mpctx->playback_pts);
+ MP_VERBOSE(mpctx, "playback restart complete @ %f, audio=%s, video=%s\n",
+ mpctx->playback_pts, mp_status_str(mpctx->video_status),
+ mp_status_str(mpctx->audio_status));
// Continuous seeks past EOF => treat as EOF instead of repeating seek.
if (mpctx->seek.type == MPSEEK_RELATIVE && mpctx->seek.amount > 0 &&
diff --git a/player/video.c b/player/video.c
index 5d9879bc51..d1e1554ef0 100644
--- a/player/video.c
+++ b/player/video.c
@@ -261,7 +261,7 @@ void reinit_video_chain_src(struct MPContext *mpctx, struct track *track)
vo_c->dec_src = track->dec->f->pins[0];
vo_c->filter->container_fps = track->dec->fps;
vo_c->is_coverart = !!track->stream->attached_picture;
- vo_c->is_sparse = track->stream->still_image;
+ vo_c->is_sparse = track->stream->still_image || vo_c->is_coverart;
track->vo_c = vo_c;
vo_c->track = track;
@@ -402,23 +402,28 @@ static void shift_frames(struct MPContext *mpctx)
mpctx->num_next_frames -= 1;
}
+static bool use_video_lookahead(struct MPContext *mpctx)
+{
+ return mpctx->video_out &&
+ !(mpctx->video_out->driver->caps & VO_CAP_NORETAIN) &&
+ !(mpctx->opts->untimed || mpctx->video_out->driver->untimed) &&
+ !mpctx->opts->video_latency_hacks;
+}
+
static int get_req_frames(struct MPContext *mpctx, bool eof)
{
// On EOF, drain all frames.
if (eof)
return 1;
- if (mpctx->video_out->driver->caps & VO_CAP_NORETAIN)
+ if (!use_video_lookahead(mpctx))
return 1;
if (mpctx->vo_chain && mpctx->vo_chain->is_sparse)
return 1;
- if (mpctx->opts->untimed || mpctx->video_out->driver->untimed)
- return 1;
-
// Normally require at least 2 frames, so we can compute a frame duration.
- int min = mpctx->opts->video_latency_hacks ? 1 : 2;
+ int min = 2;
// On the first frame, output a new frame as quickly as possible.
if (mpctx->video_pts == MP_NOPTS_VALUE)
@@ -452,6 +457,7 @@ static bool have_new_frame(struct MPContext *mpctx, bool eof)
}
// Fill mpctx->next_frames[] with a newly filtered or decoded image.
+// logical_eof: is set to true if there is EOF after currently queued frames
// returns VD_* code
static int video_output_image(struct MPContext *mpctx, bool *logical_eof)
{
@@ -471,6 +477,7 @@ static int video_output_image(struct MPContext *mpctx, bool *logical_eof)
}
if (vo_c->is_coverart) {
+ *logical_eof = true;
if (vo_has_frame(mpctx->video_out))
return VD_EOF;
hrseek = false;
@@ -533,9 +540,10 @@ static int video_output_image(struct MPContext *mpctx, bool *logical_eof)
if (!hrseek)
mp_image_unrefp(&mpctx->saved_frame);
- // If hr-seek went past EOF, use the last frame.
- if (mpctx->saved_frame && r == VD_EOF) {
- add_new_frame(mpctx, mpctx->saved_frame);
+ if (r == VD_EOF) {
+ // If hr-seek went past EOF, use the last frame.
+ if (mpctx->saved_frame)
+ add_new_frame(mpctx, mpctx->saved_frame);
mpctx->saved_frame = NULL;
*logical_eof = true;
}
@@ -1010,7 +1018,8 @@ void write_video(struct MPContext *mpctx)
bool logical_eof = false;
int r = video_output_image(mpctx, &logical_eof);
- MP_TRACE(mpctx, "video_output_image: %d\n", r);
+ MP_TRACE(mpctx, "video_output_image: r=%d/eof=%d/st=%s\n", r, logical_eof,
+ mp_status_str(mpctx->video_status));
if (r < 0)
goto error;
@@ -1039,9 +1048,7 @@ void write_video(struct MPContext *mpctx)
if (mpctx->video_status <= STATUS_PLAYING) {
mpctx->video_status = STATUS_DRAINING;
get_relative_time(mpctx);
- if (mpctx->num_past_frames == 1 && mpctx->past_frames[0].pts == 0 &&
- !mpctx->ao_chain)
- {
+ if (vo_c->is_sparse && !mpctx->ao_chain) {
MP_VERBOSE(mpctx, "assuming this is an image\n");
mpctx->time_frame += opts->image_display_duration;
} else if (mpctx->last_frame_duration > 0) {
@@ -1082,6 +1089,20 @@ void write_video(struct MPContext *mpctx)
return;
}
+ if (logical_eof && !mpctx->num_past_frames && mpctx->num_next_frames == 1 &&
+ use_video_lookahead(mpctx) && !vo_c->is_sparse)
+ {
+ // Too much danger to accidentally mark video as sparse when e.g.
+ // seeking exactly to the last frame, so as a heuristic, do this only
+ // if it looks like the "first" video frame (unreliable, but often
+ // works out well). Helps with seeking with single-image video tracks,
+ // as well as detecting whether as video track is really an image.
+ if (mpctx->next_frames[0]->pts == 0) {
+ MP_VERBOSE(mpctx, "assuming single-image video stream\n");
+ vo_c->is_sparse = true;
+ }
+ }
+
// Filter output is different from VO input?
struct mp_image_params p = mpctx->next_frames[0]->params;
if (!vo->params || !mp_image_params_equal(&p, vo->params)) {
@@ -1199,17 +1220,10 @@ void write_video(struct MPContext *mpctx)
mp_notify(mpctx, MPV_EVENT_TICK, NULL);
- if (vo_c->filter->got_output_eof && !mpctx->num_next_frames &&
- mpctx->ao_chain)
- {
- MP_VERBOSE(mpctx, "assuming this was the last video frame\n");
- // The main point of doing this is to prevent use of this for the
- // playback_pts if audio is still running (=> seek behavior).
- mpctx->video_status = STATUS_EOF;
- }
-
- // hr-seek past EOF -> returns last frame, but terminates playback.
- if (logical_eof)
+ // hr-seek past EOF -> returns last frame, but terminates playback. The
+ // early EOF is needed to trigger the exit before the next seek is executed.
+ // Always using early EOF breaks other cases, like images.
+ if (logical_eof && !mpctx->num_next_frames && mpctx->ao_chain)
mpctx->video_status = STATUS_EOF;
if (mpctx->video_status != STATUS_EOF) {