Skip to content

Commit

Permalink
command: let frame-step go through multiple frames
Browse files Browse the repository at this point in the history
This commit gives the frame-step command the ability to accept an
additional argument specifying the amount of frames (forwards or
backwards) to step through. By default, it steps forward 1. The
frame-back-step command is reworked to simply be a call to frame-step
with a -1 value. In the case where the value is exactly 1, mpv will play
exactly 1 frame and stop like before. However if a backwards step is
requested or multiple frames forward, then mpv will perform a very exact
seek. This working well, of course, depends on the pts values in the
stream being reliable which may not be the case. As a minor note,
MPSEEK_BACKSTEP is renamed to MPSEEK_FRAMESTEP since forward seeks use
this as well. Fixes mpv-player#10128.
  • Loading branch information
Dudemanguy committed Apr 25, 2022
1 parent 6407095 commit 4491ab8
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 33 deletions.
2 changes: 2 additions & 0 deletions DOCS/interface-changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Interface changes
- add `dolbyvision` sub-parameter to `format` video filter
- `--sub-visibility` no longer has any effect on secondary subtitles
- add `film-grain` sub-parameter to `format` video filter
- add optional `frames` argument to `frame-step` command controlling the direction
and amount of frames mpv steps through
--- mpv 0.34.0 ---
- deprecate selecting by card number with `--drm-connector`, add
`--drm-device` which can be used instead
Expand Down
26 changes: 16 additions & 10 deletions DOCS/man/input.rst
Original file line number Diff line number Diff line change
Expand Up @@ -316,18 +316,24 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).

Using it without any arguments gives you the default behavior.

``frame-step``
Play one frame, then pause. Does nothing with audio-only playback.

``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.
``frame-step [<frames>]``
Go forward or backwards by a given amount of frames. If ``<frames>`` is
omitted, the value is assumed to be ``1``. 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
post-processing that modifies timing of frames (e.g. deinterlacing) should
usually work, but might make backstepping silently behave incorrectly in
corner cases. Using ``--hr-seek-framedrop=no`` should help, although it
might make precise seeking slower.
usually work, but might make backstepping silently behave incorrectly in corner
cases. Using ``--hr-seek-framedrop=no`` should help, although it might make
precise seeking slower.

Note that in the special case of stepping forward exactly 1 frame, mpv will not
perform a seek but instead play exactly one frame and then pause.

This does not work with audio-only playback.

``frame-back-step``
Calls ``frame-step`` with a value of ``-1``.

This does not work with audio-only playback.

Expand Down
27 changes: 13 additions & 14 deletions player/command.c
Original file line number Diff line number Diff line change
Expand Up @@ -4970,39 +4970,35 @@ static void cmd_frame_step(void *p)
{
struct mp_cmd_ctx *cmd = p;
struct MPContext *mpctx = cmd->mpctx;
int frames = cmd->args[0].v.i;

if (!mpctx->playback_initialized) {
if (!mpctx->playback_initialized || frames == 0) {
cmd->success = false;
return;
}

if (cmd->cmd->is_up_down) {
if (frames > 0 && cmd->cmd->is_up_down) {
if (cmd->cmd->is_up) {
if (mpctx->step_frames < 1)
set_pause_state(mpctx, true);
} else {
if (cmd->cmd->repeated) {
set_pause_state(mpctx, false);
} else {
add_step_frame(mpctx, 1);
add_step_frame(mpctx, frames);
}
}
} else {
add_step_frame(mpctx, 1);
add_step_frame(mpctx, frames);
}
}

static void cmd_frame_back_step(void *p)
{
struct mp_cmd_ctx *cmd = p;
struct MPContext *mpctx = cmd->mpctx;

if (!mpctx->playback_initialized) {
cmd->success = false;
return;
}

add_step_frame(mpctx, -1);
struct mp_cmd_arg arg = {.v.i = -1};
cmd->args = &arg;
cmd_frame_step(cmd);
}

static void cmd_quit(void *p)
Expand Down Expand Up @@ -6066,8 +6062,11 @@ const struct mp_cmd_def mp_cmds[] = {
{ "stop", cmd_stop,
{ {"flags", OPT_FLAGS(v.i, {"keep-playlist", 1}), .flags = MP_CMD_OPT_ARG} }
},
{ "frame-step", cmd_frame_step, .allow_auto_repeat = true,
.on_updown = true },
{ "frame-step", cmd_frame_step,
{ {"frames", OPT_INT(v.i), OPTDEF_INT(1)} },
.allow_auto_repeat = true,
.on_updown = true
},
{ "frame-back-step", cmd_frame_back_step, .allow_auto_repeat = true },
{ "playlist-next", cmd_playlist_next_prev,
{
Expand Down
2 changes: 1 addition & 1 deletion player/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ enum seek_type {
MPSEEK_RELATIVE,
MPSEEK_ABSOLUTE,
MPSEEK_FACTOR,
MPSEEK_BACKSTEP,
MPSEEK_FRAMESTEP,
};

enum seek_precision {
Expand Down
29 changes: 21 additions & 8 deletions player/playloop.c
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,15 @@ void add_step_frame(struct MPContext *mpctx, int dir)
{
if (!mpctx->vo_chain)
return;
if (dir > 0) {
mpctx->step_frames += 1;
if (dir == 1) {
mpctx->step_frames += dir;
set_pause_state(mpctx, false);
} else if (dir < 0) {
} else {
// Offset the backstep frame amount by 1.
if (dir < 0)
dir += 1;
if (!mpctx->hrseek_active) {
queue_seek(mpctx, MPSEEK_BACKSTEP, 0, MPSEEK_VERY_EXACT, 0);
queue_seek(mpctx, MPSEEK_FRAMESTEP, dir, MPSEEK_VERY_EXACT, 0);
set_pause_state(mpctx, true);
}
}
Expand Down Expand Up @@ -258,6 +261,15 @@ void reset_playback_state(struct MPContext *mpctx)
update_core_idle_state(mpctx);
}

static double calculate_framestep_pts(MPContext *mpctx, double current_time,
int step_frames)
{
// Crude guess at the pts.
int previous_frame = mpctx->num_past_frames - 1;
double pts = mpctx->past_frames[previous_frame].approx_duration * step_frames;
return current_time + pts;
}

static void mp_seek(MPContext *mpctx, struct seek_params seek)
{
struct MPOpts *opts = mpctx->opts;
Expand All @@ -278,8 +290,9 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
case MPSEEK_ABSOLUTE:
seek_pts = seek.amount;
break;
case MPSEEK_BACKSTEP:
seek_pts = current_time;
case MPSEEK_FRAMESTEP:
seek_pts = calculate_framestep_pts(mpctx, current_time,
(int)seek.amount);
hr_seek_very_exact = true;
break;
case MPSEEK_RELATIVE:
Expand Down Expand Up @@ -388,7 +401,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_backstep = seek.type == MPSEEK_FRAMESTEP;
mpctx->hrseek_pts = seek_pts * mpctx->play_dir;

// allow decoder to drop frames before hrseek_pts
Expand Down Expand Up @@ -446,7 +459,7 @@ void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount,
return;
case MPSEEK_ABSOLUTE:
case MPSEEK_FACTOR:
case MPSEEK_BACKSTEP:
case MPSEEK_FRAMESTEP:
*seek = (struct seek_params) {
.type = type,
.amount = amount,
Expand Down

0 comments on commit 4491ab8

Please sign in to comment.