mirror of
https://github.com/mpv-player/mpv
synced 2025-03-20 02:09:52 +00:00
core: add support for precise non-keyframe-limited seeks
Add support for seeking to an arbitrary non-keyframe position by decoding video starting from the previous keyframe. Whether to use this functionality when seeking is controlled by the new option -hr-seek and a new third argument to the "seek" command. The default is to use it for absolute seeks (like chapter seeks) but not for relative ones. Because there's currently no support for cutting encoded audio some desync is expected if encoded audio passthrough is used. Currently precise seeks always go to the first frame with timestamp equal to or greater than the target position; there's no support for "matching or earlier" backwards seeks at frame level.
This commit is contained in:
parent
23f598e0ee
commit
f0649f13d6
@ -909,6 +909,27 @@ mplayer \-heartbeat\-cmd "gnome\-screensaver\-command \-p" file
|
||||
.PD 1
|
||||
.
|
||||
.TP
|
||||
.B \-hr\-seek off|absolute|always
|
||||
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 some video formats precise seeks are disabled. This option selects the
|
||||
default choice to use for seeks; it's possible to explicitly override that
|
||||
default in the definition of key bindings and in slave mode commands.
|
||||
.PD 0
|
||||
.RSs
|
||||
.IPs off
|
||||
Never use precise seeks.
|
||||
.IPs 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).
|
||||
.IPs always
|
||||
Use precise seeks whenever possible.
|
||||
.RE
|
||||
.PD 1
|
||||
.
|
||||
.TP
|
||||
.B \-identify
|
||||
Shorthand for \-msglevel identify=4.
|
||||
Show file parameters in an easily parseable format.
|
||||
|
@ -272,11 +272,16 @@ radio_step_channel <-1|1>
|
||||
radio_step_freq <value>
|
||||
Tune frequency by the <value> (positive - up, negative - down).
|
||||
|
||||
seek <value> [type]
|
||||
seek <value> [type] [hr-seek]
|
||||
Seek to some place in the movie.
|
||||
0 is a relative seek of +/- <value> seconds (default).
|
||||
1 is a seek to <value> % in the movie.
|
||||
2 is a seek to an absolute position of <value> seconds.
|
||||
type = 0 is a relative seek of +/- <value> seconds (default).
|
||||
type = 1 is a seek to <value> % in the movie.
|
||||
type = 2 is a seek to an absolute position of <value> seconds.
|
||||
The hr-seek parameter controls whether to use precise seeks (not limited
|
||||
to keyframe positions in video).
|
||||
hr-seek = 0 means use default set with option -hr-seek (default).
|
||||
hr-seek = 1 means force precise seek if possible.
|
||||
hr-seek = -1 means force non-precise seek.
|
||||
|
||||
seek_chapter <value> [type]
|
||||
Seek to the start of a chapter.
|
||||
@ -513,6 +518,7 @@ name type min max get set step comment
|
||||
osdlevel int 0 3 X X X as -osdlevel
|
||||
speed float 0.01 100 X X X as -speed
|
||||
loop int -1 X X X as -loop
|
||||
hr_seek string X X X as -hr-seek
|
||||
pts_association_mode string X X X as -pts-association-mode
|
||||
pause flag 0 1 X 1 if paused, use with pausing_keep_force
|
||||
filename string X file playing wo path
|
||||
|
@ -312,6 +312,8 @@ const m_option_t mplayer_opts[]={
|
||||
OPT_CHOICE("pts-association-mode", user_pts_assoc_mode, 0,
|
||||
({"auto", 0}, {"decoder", 1}, {"sort", 2})),
|
||||
OPT_MAKE_FLAGS("initial-audio-sync", initial_audio_sync, 0),
|
||||
OPT_CHOICE("hr-seek", hr_seek, 0,
|
||||
({"off", -1}, {"absolute", 0}, {"always", 1}, {"on", 1})),
|
||||
OPT_FLAG_CONSTANTS("noautosync", autosync, 0, 0, -1),
|
||||
OPT_INTRANGE("autosync", autosync, 0, 0, 10000),
|
||||
|
||||
|
16
command.c
16
command.c
@ -2219,6 +2219,8 @@ static const m_option_t mp_properties[] = {
|
||||
M_OPT_RANGE, 0, 1, NULL },
|
||||
{ "pts_association_mode", mp_property_generic_option, &m_option_type_choice,
|
||||
0, 0, 0, "pts-association-mode" },
|
||||
{ "hr_seek", mp_property_generic_option, &m_option_type_choice,
|
||||
0, 0, 0, "hr-seek" },
|
||||
|
||||
// Audio
|
||||
{ "volume", mp_property_volume, CONF_TYPE_FLOAT,
|
||||
@ -2393,6 +2395,7 @@ static struct property_osd_display {
|
||||
{ "chapter", -1, -1, NULL },
|
||||
{ "capturing", 0, -1, _("Capturing: %s") },
|
||||
{ "pts_association_mode", 0, -1, "PTS association mode: %s" },
|
||||
{ "hr_seek", 0, -1, "hr-seek: %s" },
|
||||
// audio
|
||||
{ "volume", OSD_VOLUME, -1, _("Volume") },
|
||||
{ "mute", 0, -1, _("Mute: %s") },
|
||||
@ -2713,20 +2716,19 @@ void run_command(MPContext *mpctx, mp_cmd_t *cmd)
|
||||
if (!set_property_command(mpctx, cmd))
|
||||
switch (cmd->id) {
|
||||
case MP_CMD_SEEK:{
|
||||
float v;
|
||||
int abs;
|
||||
mpctx->add_osd_seek_info = true;
|
||||
v = cmd->args[0].v.f;
|
||||
abs = (cmd->nargs > 1) ? cmd->args[1].v.i : 0;
|
||||
float v = cmd->args[0].v.f;
|
||||
int abs = (cmd->nargs > 1) ? cmd->args[1].v.i : 0;
|
||||
int exact = (cmd->nargs > 2) ? cmd->args[2].v.i : 0;
|
||||
if (abs == 2) { /* Absolute seek to a specific timestamp in seconds */
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, v, 0);
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, v, exact);
|
||||
mpctx->osd_function = v > get_current_time(mpctx) ?
|
||||
OSD_FFW : OSD_REW;
|
||||
} else if (abs) { /* Absolute seek by percentage */
|
||||
queue_seek(mpctx, MPSEEK_FACTOR, v / 100.0, 0);
|
||||
queue_seek(mpctx, MPSEEK_FACTOR, v / 100.0, exact);
|
||||
mpctx->osd_function = OSD_FFW; // Direction isn't set correctly
|
||||
} else {
|
||||
queue_seek(mpctx, MPSEEK_RELATIVE, v, 0);
|
||||
queue_seek(mpctx, MPSEEK_RELATIVE, v, exact);
|
||||
mpctx->osd_function = (v > 0) ? OSD_FFW : OSD_REW;
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ static const mp_cmd_t mp_cmds[] = {
|
||||
{ MP_CMD_RADIO_SET_FREQ, "radio_set_freq", 1, { {MP_CMD_ARG_FLOAT,{0}}, {-1,{0}} } },
|
||||
{ MP_CMD_RADIO_STEP_FREQ, "radio_step_freq", 1, { {MP_CMD_ARG_FLOAT,{0}}, {-1,{0}} } },
|
||||
#endif
|
||||
{ MP_CMD_SEEK, "seek", 1, { {MP_CMD_ARG_FLOAT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
|
||||
{ MP_CMD_SEEK, "seek", 1, { {MP_CMD_ARG_FLOAT,{0}}, {MP_CMD_ARG_INT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
|
||||
{ MP_CMD_EDL_MARK, "edl_mark", 0, { {-1,{0}} } },
|
||||
{ MP_CMD_AUDIO_DELAY, "audio_delay", 1, { {MP_CMD_ARG_FLOAT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
|
||||
{ MP_CMD_SPEED_INCR, "speed_incr", 1, { {MP_CMD_ARG_FLOAT,{0}}, {-1,{0}} } },
|
||||
|
@ -129,6 +129,9 @@ typedef struct MPContext {
|
||||
* stream by cutting samples or adding silence at the beginning to make
|
||||
* audio playback position match video position. */
|
||||
bool syncing_audio;
|
||||
bool hrseek_active;
|
||||
bool hrseek_framedrop;
|
||||
double hrseek_pts;
|
||||
// AV sync: the next frame should be shown when the audio out has this
|
||||
// much (in seconds) buffered data left. Increased when more data is
|
||||
// written to the ao, decreased when moving to the next frame.
|
||||
|
45
mplayer.c
45
mplayer.c
@ -2558,7 +2558,8 @@ static double update_video(struct MPContext *mpctx)
|
||||
|
||||
while (1) {
|
||||
current_module = "filter_video";
|
||||
if (vo_get_buffered_frame(video_out, false) >= 0)
|
||||
if (!mpctx->hrseek_active
|
||||
&& vo_get_buffered_frame(video_out, false) >= 0)
|
||||
break;
|
||||
// XXX Time used in this call is not counted in any performance
|
||||
// timer now
|
||||
@ -2578,7 +2579,10 @@ static double update_video(struct MPContext *mpctx)
|
||||
if (in_size > max_framesize)
|
||||
max_framesize = in_size;
|
||||
current_module = "decode video";
|
||||
int framedrop_type = check_framedrop(mpctx, sh_video->frametime);
|
||||
if (pts >= mpctx->hrseek_pts - .005)
|
||||
mpctx->hrseek_framedrop = false;
|
||||
int framedrop_type = mpctx->hrseek_framedrop ? 1 :
|
||||
check_framedrop(mpctx, sh_video->frametime);
|
||||
void *decoded_frame = decode_video(sh_video, packet, in_size,
|
||||
framedrop_type, pts);
|
||||
if (decoded_frame) {
|
||||
@ -2603,6 +2607,9 @@ static double update_video(struct MPContext *mpctx)
|
||||
if (pts == MP_NOPTS_VALUE)
|
||||
pts = sh_video->last_pts;
|
||||
}
|
||||
if (mpctx->hrseek_active && pts < mpctx->hrseek_pts - .005)
|
||||
return 0;
|
||||
mpctx->hrseek_active = false;
|
||||
sh_video->pts = pts;
|
||||
if (sh_video->last_pts == MP_NOPTS_VALUE)
|
||||
sh_video->last_pts = sh_video->pts;
|
||||
@ -2808,6 +2815,8 @@ static void seek_reset(struct MPContext *mpctx)
|
||||
|
||||
edl_seek_reset(mpctx);
|
||||
|
||||
mpctx->hrseek_active = false;
|
||||
mpctx->hrseek_framedrop = false;
|
||||
mpctx->total_avsync_change = 0;
|
||||
audio_time_usage = 0; video_time_usage = 0; vout_time_usage = 0;
|
||||
drop_frame_cnt = 0;
|
||||
@ -2854,9 +2863,15 @@ static double timeline_set_from_time(struct MPContext *mpctx, double pts,
|
||||
// return -1 if seek failed (non-seekable stream?), 0 otherwise
|
||||
static int seek(MPContext *mpctx, struct seek_params seek)
|
||||
{
|
||||
struct MPOpts *opts = &mpctx->opts;
|
||||
|
||||
current_module = "seek";
|
||||
if (mpctx->stop_play == AT_END_OF_FILE)
|
||||
mpctx->stop_play = KEEP_PLAYING;
|
||||
bool hr_seek = mpctx->demuxer->accurate_seek && opts->correct_pts;
|
||||
hr_seek &= seek.exact >= 0 && seek.type != MPSEEK_FACTOR;
|
||||
hr_seek &= opts->hr_seek == 0 && seek.type == MPSEEK_ABSOLUTE
|
||||
|| opts->hr_seek > 0 || seek.exact > 0;
|
||||
if (seek.type == MPSEEK_FACTOR
|
||||
|| seek.type == MPSEEK_ABSOLUTE
|
||||
&& seek.amount < mpctx->last_chapter_pts
|
||||
@ -2899,7 +2914,7 @@ static int seek(MPContext *mpctx, struct seek_params seek)
|
||||
case MPSEEK_ABSOLUTE:
|
||||
demuxer_style |= SEEK_ABSOLUTE;
|
||||
}
|
||||
if (seek.direction < 0)
|
||||
if (hr_seek || seek.direction < 0)
|
||||
demuxer_style |= SEEK_BACKWARD;
|
||||
else if (seek.direction > 0)
|
||||
demuxer_style |= SEEK_FORWARD;
|
||||
@ -2918,6 +2933,12 @@ static int seek(MPContext *mpctx, struct seek_params seek)
|
||||
if (seek.type == MPSEEK_ABSOLUTE)
|
||||
mpctx->video_pts = seek.amount;
|
||||
|
||||
if (hr_seek) {
|
||||
mpctx->hrseek_active = true;
|
||||
mpctx->hrseek_framedrop = true;
|
||||
mpctx->hrseek_pts = seek.amount;
|
||||
}
|
||||
|
||||
mpctx->start_timestamp = GetTimerMS();
|
||||
|
||||
return 0;
|
||||
@ -3129,9 +3150,10 @@ static void run_playloop(struct MPContext *mpctx)
|
||||
vo_fps = mpctx->sh_video->fps;
|
||||
|
||||
bool blit_frame = mpctx->video_out->frame_loaded;
|
||||
if (!blit_frame) {
|
||||
if (!blit_frame || mpctx->hrseek_active) {
|
||||
double frame_time = update_video(mpctx);
|
||||
blit_frame = mpctx->video_out->frame_loaded;
|
||||
blit_frame &= !mpctx->hrseek_active;
|
||||
mp_dbg(MSGT_AVSYNC, MSGL_DBG2, "*** ftime=%5.3f ***\n", frame_time);
|
||||
if (mpctx->sh_video->vf_initialized < 0) {
|
||||
mp_tmsg(MSGT_CPLAYER, MSGL_FATAL,
|
||||
@ -4547,6 +4569,15 @@ if(play_n_frames==0){
|
||||
mpctx->stop_play=PT_NEXT_ENTRY; goto goto_next_file;
|
||||
}
|
||||
|
||||
mpctx->time_frame = 0;
|
||||
mpctx->drop_message_shown = 0;
|
||||
mpctx->restart_playback = true;
|
||||
mpctx->video_pts = 0;
|
||||
mpctx->hrseek_active = false;
|
||||
mpctx->hrseek_framedrop = false;
|
||||
mpctx->total_avsync_change = 0;
|
||||
mpctx->last_chapter_seek = -1;
|
||||
|
||||
// If there's a timeline force an absolute seek to initialize state
|
||||
if (seek_to_sec || mpctx->timeline) {
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, seek_to_sec, 0);
|
||||
@ -4577,12 +4608,6 @@ if (mpctx->stream->type == STREAMTYPE_DVDNAV) {
|
||||
|
||||
mpctx->seek = (struct seek_params){0};
|
||||
get_relative_time(mpctx); // reset current delta
|
||||
mpctx->time_frame = 0;
|
||||
mpctx->drop_message_shown = 0;
|
||||
mpctx->restart_playback = true;
|
||||
mpctx->video_pts = 0;
|
||||
mpctx->total_avsync_change = 0;
|
||||
mpctx->last_chapter_seek = -1;
|
||||
// Make sure VO knows current pause state
|
||||
if (mpctx->sh_video)
|
||||
vo_control(mpctx->video_out, mpctx->paused ? VOCTRL_PAUSE : VOCTRL_RESUME,
|
||||
|
Loading…
Reference in New Issue
Block a user