summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/man/en/input.rst11
-rw-r--r--core/command.c6
-rw-r--r--core/input/input.c1
-rw-r--r--core/input/input.h1
-rw-r--r--core/mp_core.h15
-rw-r--r--core/mplayer.c99
-rw-r--r--etc/input.conf1
7 files changed, 126 insertions, 8 deletions
diff --git a/DOCS/man/en/input.rst b/DOCS/man/en/input.rst
index 339168b3c4..7938502573 100644
--- a/DOCS/man/en/input.rst
+++ b/DOCS/man/en/input.rst
@@ -79,6 +79,17 @@ seek <seconds> [relative|absolute|absolute-percent|- [default-precise|exact|keyf
frame_step
Play one frame, then pause.
+frame_back_step
+ Go back by one frame, then pause. Note that this can be very slow (it tries
+ to be precise, not fast), and sometimes fails to behave as expected. How
+ well this works depends on whether precise seeking works correctly (e.g.
+ see the ``--hr-seek-demuxer-offset`` option). Video filters or other video
+ postprocessing that modifies timing of frames (e.g. deinterlacing) should
+ usually work, but might make backstepping silently behave incorrectly in
+ corner cases.
+
+ This doesn't work with audio-only playback.
+
set <property> "<value>"
Set the given property to the given value.
diff --git a/core/command.c b/core/command.c
index 6b00c9136f..11d987819d 100644
--- a/core/command.c
+++ b/core/command.c
@@ -1832,7 +1832,11 @@ void run_command(MPContext *mpctx, mp_cmd_t *cmd)
}
case MP_CMD_FRAME_STEP:
- add_step_frame(mpctx);
+ add_step_frame(mpctx, 1);
+ break;
+
+ case MP_CMD_FRAME_BACK_STEP:
+ add_step_frame(mpctx, -1);
break;
case MP_CMD_QUIT:
diff --git a/core/input/input.c b/core/input/input.c
index 17ad7c3aeb..31b807d92f 100644
--- a/core/input/input.c
+++ b/core/input/input.c
@@ -128,6 +128,7 @@ static const mp_cmd_t mp_cmds[] = {
{ MP_CMD_QUIT, "quit", { OARG_INT(0) } },
{ MP_CMD_STOP, "stop", },
{ MP_CMD_FRAME_STEP, "frame_step", },
+ { MP_CMD_FRAME_BACK_STEP, "frame_back_step", },
{ MP_CMD_PLAYLIST_NEXT, "playlist_next", {
OARG_CHOICE(0, ({"weak", 0}, {"0", 0},
{"force", 1}, {"1", 1})),
diff --git a/core/input/input.h b/core/input/input.h
index 30cc2ce9ed..ff79222c9b 100644
--- a/core/input/input.h
+++ b/core/input/input.h
@@ -44,6 +44,7 @@ enum mp_command_type {
MP_CMD_TV_SET_FREQ,
MP_CMD_TV_SET_NORM,
MP_CMD_FRAME_STEP,
+ MP_CMD_FRAME_BACK_STEP,
MP_CMD_SPEED_MULT,
MP_CMD_RUN,
MP_CMD_SUB_ADD,
diff --git a/core/mp_core.h b/core/mp_core.h
index 452cf70d58..82942c32cb 100644
--- a/core/mp_core.h
+++ b/core/mp_core.h
@@ -117,6 +117,10 @@ struct track {
struct sub_data *subdata;
};
+enum {
+ MAX_NUM_VO_PTS = 100,
+};
+
typedef struct MPContext {
struct MPOpts opts;
struct m_config *mconfig;
@@ -219,6 +223,15 @@ typedef struct MPContext {
// Video PTS, or audio PTS if video has ended.
double playback_pts;
+ // History of video frames timestamps that were queued in the VO
+ // This includes even skipped frames during hr-seek
+ double vo_pts_history_pts[MAX_NUM_VO_PTS];
+ // Whether the PTS at vo_pts_history[n] is after a seek reset
+ uint64_t vo_pts_history_seek[MAX_NUM_VO_PTS];
+ uint64_t vo_pts_history_seek_ts;
+ uint64_t backstep_start_seek_ts;
+ bool backstep_active;
+
float audio_delay;
unsigned int last_heartbeat;
@@ -287,7 +300,7 @@ struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename,
int reinit_video_chain(struct MPContext *mpctx);
void pause_player(struct MPContext *mpctx);
void unpause_player(struct MPContext *mpctx);
-void add_step_frame(struct MPContext *mpctx);
+void add_step_frame(struct MPContext *mpctx, int dir);
void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount,
int exact);
int seek_chapter(struct MPContext *mpctx, int chapter, double *seek_pts);
diff --git a/core/mplayer.c b/core/mplayer.c
index aa7a538a73..08a3d8256d 100644
--- a/core/mplayer.c
+++ b/core/mplayer.c
@@ -2404,6 +2404,7 @@ int reinit_video_chain(struct MPContext *mpctx)
sh_video->next_frame_time = 0;
mpctx->restart_playback = true;
mpctx->delay = 0;
+ mpctx->vo_pts_history_seek_ts++;
// ========== Init display (sh_video->disp_w*sh_video->disp_h/out_fmt) ============
@@ -2419,6 +2420,40 @@ no_video:
return 0;
}
+static void add_frame_pts(struct MPContext *mpctx, double pts)
+{
+ if (pts == MP_NOPTS_VALUE || mpctx->hrseek_framedrop) {
+ mpctx->vo_pts_history_seek_ts++; // mark discontinuity
+ return;
+ }
+ for (int n = MAX_NUM_VO_PTS - 1; n >= 1; n--) {
+ mpctx->vo_pts_history_seek[n] = mpctx->vo_pts_history_seek[n - 1];
+ mpctx->vo_pts_history_pts[n] = mpctx->vo_pts_history_pts[n - 1];
+ }
+ mpctx->vo_pts_history_seek[0] = mpctx->vo_pts_history_seek_ts;
+ mpctx->vo_pts_history_pts[0] = pts;
+}
+
+static double find_previous_pts(struct MPContext *mpctx, double pts)
+{
+ for (int n = 0; n < MAX_NUM_VO_PTS - 1; n++) {
+ if (pts == mpctx->vo_pts_history_pts[n] &&
+ mpctx->vo_pts_history_seek[n] != 0 &&
+ mpctx->vo_pts_history_seek[n] == mpctx->vo_pts_history_seek[n + 1])
+ {
+ return mpctx->vo_pts_history_pts[n + 1];
+ }
+ }
+ return MP_NOPTS_VALUE;
+}
+
+static double get_last_frame_pts(struct MPContext *mpctx)
+{
+ if (mpctx->vo_pts_history_seek[0] == mpctx->vo_pts_history_seek_ts)
+ return mpctx->vo_pts_history_pts[0];
+ return MP_NOPTS_VALUE;
+}
+
static bool filter_output_queued_frame(struct MPContext *mpctx)
{
struct sh_video *sh_video = mpctx->sh_video;
@@ -2571,6 +2606,7 @@ static double update_video(struct MPContext *mpctx)
if (pts == MP_NOPTS_VALUE)
pts = sh_video->last_pts;
}
+ add_frame_pts(mpctx, pts);
if (mpctx->hrseek_active && pts < mpctx->hrseek_pts - .005) {
vo_skip_frame(video_out);
return 0;
@@ -2658,12 +2694,20 @@ static bool redraw_osd(struct MPContext *mpctx)
return true;
}
-void add_step_frame(struct MPContext *mpctx)
+void add_step_frame(struct MPContext *mpctx, int dir)
{
- mpctx->step_frames++;
- if (mpctx->video_out && mpctx->sh_video && mpctx->video_out->config_ok)
- vo_control(mpctx->video_out, VOCTRL_PAUSE, NULL);
- unpause_player(mpctx);
+ if (dir > 0) {
+ mpctx->step_frames += 1;
+ if (mpctx->video_out && mpctx->sh_video && mpctx->video_out->config_ok)
+ vo_control(mpctx->video_out, VOCTRL_PAUSE, NULL);
+ unpause_player(mpctx);
+ } else if (dir < 0) {
+ if (!mpctx->backstep_active && !mpctx->hrseek_active) {
+ mpctx->backstep_active = true;
+ mpctx->backstep_start_seek_ts = mpctx->vo_pts_history_seek_ts;
+ pause_player(mpctx);
+ }
+ }
}
static void seek_reset(struct MPContext *mpctx, bool reset_ao, bool reset_ac)
@@ -2701,6 +2745,8 @@ static void seek_reset(struct MPContext *mpctx, bool reset_ao, bool reset_ac)
mpctx->drop_frame_cnt = 0;
mpctx->dropped_frames = 0;
mpctx->playback_pts = MP_NOPTS_VALUE;
+ mpctx->vo_pts_history_seek_ts++;
+ mpctx->backstep_active = false;
#ifdef CONFIG_ENCODING
encode_lavc_discontinuity(mpctx->encode_lavc_ctx);
@@ -3488,7 +3534,7 @@ static void run_playloop(struct MPContext *mpctx)
sleeptime = 0;
} else if (mpctx->paused && video_left) {
// force redrawing OSD by framestepping
- add_step_frame(mpctx);
+ add_step_frame(mpctx, 1);
sleeptime = 0;
}
}
@@ -3525,6 +3571,46 @@ static void run_playloop(struct MPContext *mpctx)
break;
}
+ if (mpctx->backstep_active) {
+ double current_pts = mpctx->last_vo_pts;
+ mpctx->backstep_active = false;
+ if (mpctx->sh_video && current_pts != MP_NOPTS_VALUE) {
+ double seek_pts = find_previous_pts(mpctx, current_pts);
+ if (seek_pts != MP_NOPTS_VALUE) {
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, seek_pts, 1);
+ } else {
+ double last = get_last_frame_pts(mpctx);
+ if (last != MP_NOPTS_VALUE && last >= current_pts &&
+ mpctx->backstep_start_seek_ts != mpctx->vo_pts_history_seek_ts)
+ {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "Backstep failed.\n");
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, current_pts, 1);
+ } else if (!mpctx->hrseek_active) {
+ mp_msg(MSGT_CPLAYER, MSGL_V, "Start backstep indexing.\n");
+ // Force it to index the video up until current_pts.
+ // The whole point is getting frames _before_ that PTS,
+ // so apply an arbitrary offset. (In theory the offset
+ // has to be large enough to reach the previous frame.)
+ seek(mpctx, (struct seek_params){
+ .type = MPSEEK_ABSOLUTE,
+ .amount = current_pts - 1.0,
+ }, false);
+ // Don't leave hr-seek mode. If all goes right, hr-seek
+ // mode is cancelled as soon as the frame before
+ // current_pts is found during hr-seeking.
+ // Note that current_pts should be part of the index,
+ // otherwise we can't find the previous frame, so set the
+ // seek target an arbitrary amount of time after it.
+ mpctx->hrseek_pts = current_pts + 10.0;
+ mpctx->hrseek_framedrop = false;
+ mpctx->backstep_active = true;
+ } else {
+ mpctx->backstep_active = true;
+ }
+ }
+ }
+ }
+
// handle -sstep
if (opts->step_sec > 0 && !mpctx->stop_play && !mpctx->paused &&
!mpctx->restart_playback)
@@ -4128,6 +4214,7 @@ goto_enable_cache: ;
mpctx->hrseek_active = false;
mpctx->hrseek_framedrop = false;
mpctx->step_frames = 0;
+ mpctx->backstep_active = false;
mpctx->total_avsync_change = 0;
mpctx->last_chapter_seek = -2;
mpctx->playing_msg_shown = false;
diff --git a/etc/input.conf b/etc/input.conf
index 8623a239d9..300f20c307 100644
--- a/etc/input.conf
+++ b/etc/input.conf
@@ -60,6 +60,7 @@ q {encode} quit
ESC quit
p cycle pause # toggle pause/playback mode
. frame_step # advance one frame and pause
+, frame_back_step # go back by one frame and pause
SPACE cycle pause
> playlist_next # skip to next file
ENTER playlist_next force # skip to next file or quit