summaryrefslogtreecommitdiffstats
path: root/player
diff options
context:
space:
mode:
Diffstat (limited to 'player')
-rw-r--r--player/command.c36
-rw-r--r--player/core.h22
-rw-r--r--player/loadfile.c3
-rw-r--r--player/osd.c6
-rw-r--r--player/video.c206
5 files changed, 267 insertions, 6 deletions
diff --git a/player/command.c b/player/command.c
index 486c7262b8..c64427f930 100644
--- a/player/command.c
+++ b/player/command.c
@@ -295,11 +295,26 @@ static int mp_property_av_speed_correction(void *ctx, struct m_property *prop,
{
MPContext *mpctx = ctx;
char *type = prop->priv;
+ double val = 0;
switch (type[0]) {
- case 'a': return m_property_double_ro(action, arg, mpctx->speed_factor_a);
- case 'v': return m_property_double_ro(action, arg, mpctx->speed_factor_v);
+ case 'a': val = mpctx->speed_factor_a; break;
+ case 'v': val = mpctx->speed_factor_v; break;
+ default: abort();
}
- abort();
+
+ if (action == M_PROPERTY_PRINT) {
+ *(char **)arg = talloc_asprintf(NULL, "%+.05f%%", (val - 1) * 100);
+ return M_PROPERTY_OK;
+ }
+
+ return m_property_double_ro(action, arg, val);
+}
+
+static int mp_property_display_sync_active(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ return m_property_flag_ro(action, arg, mpctx->display_sync_active);
}
/// filename with path (RO)
@@ -557,6 +572,16 @@ static int mp_property_vo_drop_frame_count(void *ctx, struct m_property *prop,
return m_property_int_ro(action, arg, vo_get_drop_count(mpctx->video_out));
}
+static int mp_property_vo_missed_frame_count(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ if (!mpctx->d_video)
+ return M_PROPERTY_UNAVAILABLE;
+
+ return m_property_int_ro(action, arg, vo_get_missed_count(mpctx->video_out));
+}
+
/// Current position in percent (RW)
static int mp_property_percent_pos(void *ctx, struct m_property *prop,
int action, void *arg)
@@ -3318,6 +3343,7 @@ static const struct m_property mp_properties[] = {
{"speed", mp_property_playback_speed},
{"audio-speed-correction", mp_property_av_speed_correction, .priv = "a"},
{"video-speed-correction", mp_property_av_speed_correction, .priv = "v"},
+ {"display-sync-active", mp_property_display_sync_active},
{"filename", mp_property_filename},
{"stream-open-filename", mp_property_stream_open_filename},
{"file-size", mp_property_file_size},
@@ -3335,6 +3361,7 @@ static const struct m_property mp_properties[] = {
{"total-avsync-change", mp_property_total_avsync_change},
{"drop-frame-count", mp_property_drop_frame_cnt},
{"vo-drop-frame-count", mp_property_vo_drop_frame_count},
+ {"vo-missed-frame-count", mp_property_vo_missed_frame_count},
{"percent-pos", mp_property_percent_pos},
{"time-start", mp_property_time_start},
{"time-pos", mp_property_time_pos},
@@ -3549,7 +3576,8 @@ static const char *const *const mp_event_property_change[] = {
E(MPV_EVENT_TICK, "time-pos", "stream-pos", "stream-time-pos", "avsync",
"percent-pos", "time-remaining", "playtime-remaining", "playback-time",
"estimated-vf-fps", "drop-frame-count", "vo-drop-frame-count",
- "total-avsync-change", "audio-speed-correction", "video-speed-correction"),
+ "total-avsync-change", "audio-speed-correction", "video-speed-correction",
+ "vo-missed-frame-count"),
E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params",
"video-format", "video-codec", "video-bitrate", "dwidth", "dheight",
"width", "height", "fps", "aspect", "vo-configured", "current-vo",
diff --git a/player/core.h b/player/core.h
index 092775f40e..6ac146624e 100644
--- a/player/core.h
+++ b/player/core.h
@@ -79,6 +79,22 @@ enum seek_precision {
// Comes from the assumption that some formats round timestamps to ms.
#define FRAME_DURATION_TOLERANCE 0.0011
+enum video_sync {
+ VS_DEFAULT = 0,
+ VS_DISP_RESAMPLE,
+ VS_DISP_RESAMPLE_VDROP,
+ VS_DISP_RESAMPLE_NONE,
+ VS_DISP_VDROP,
+ VS_DISP_NONE,
+ VS_NONE,
+};
+
+#define VS_IS_DISP(x) ((x) == VS_DISP_RESAMPLE || \
+ (x) == VS_DISP_RESAMPLE_VDROP || \
+ (x) == VS_DISP_RESAMPLE_NONE || \
+ (x) == VS_DISP_VDROP || \
+ (x) == VS_DISP_NONE)
+
struct track {
enum stream_type type;
@@ -244,7 +260,13 @@ typedef struct MPContext {
// Redundant values set from opts->playback_speed and speed_factor_*.
// update_playback_speed() updates them from the other fields.
double audio_speed, video_speed;
+ bool display_sync_active;
bool broken_fps_header;
+ double display_sync_frameduration;
+ int display_sync_drift_dir;
+ // Timing error (in seconds) due to rounding on vsync boundaries
+ double display_sync_error;
+ int display_sync_disable_counter;
/* Set if audio should be timed to start with video frame after seeking,
* not set when e.g. playing cover art */
bool sync_audio_to_video;
diff --git a/player/loadfile.c b/player/loadfile.c
index 23ffafc1b9..fc4c294fc7 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -1020,7 +1020,10 @@ static void play_current_file(struct MPContext *mpctx)
mpctx->max_frames = -1;
mpctx->video_speed = mpctx->audio_speed = opts->playback_speed;
mpctx->speed_factor_a = mpctx->speed_factor_v = 1.0;
+ mpctx->display_sync_frameduration = 0.0;
+ mpctx->display_sync_error = 0.0;
mpctx->broken_fps_header = false;
+ mpctx->display_sync_active = false;
mpctx->seek = (struct seek_params){ 0 };
reset_playback_state(mpctx);
diff --git a/player/osd.c b/player/osd.c
index 0bdb368f7e..ace465b18d 100644
--- a/player/osd.c
+++ b/player/osd.c
@@ -225,6 +225,12 @@ static void print_status(struct MPContext *mpctx)
{
// VO stats
if (mpctx->d_video) {
+ if (mpctx->display_sync_active) {
+ saddf(&line, " DS: %f", mpctx->speed_factor_a);
+ int64_t m = vo_get_missed_count(mpctx->video_out);
+ if (m > 0)
+ saddf(&line, " Missed: %"PRId64, m);
+ }
int64_t c = vo_get_drop_count(mpctx->video_out);
if (c > 0 || mpctx->dropped_frames_total > 0) {
saddf(&line, " Dropped: %"PRId64, c);
diff --git a/player/video.c b/player/video.c
index 13f40430de..e8f4837e69 100644
--- a/player/video.c
+++ b/player/video.c
@@ -42,6 +42,8 @@
#include "video/decode/dec_video.h"
#include "video/decode/vd.h"
#include "video/out/vo.h"
+#include "audio/filter/af.h"
+#include "audio/decode/dec_audio.h"
#include "core.h"
#include "command.h"
@@ -206,9 +208,11 @@ void reset_video_state(struct MPContext *mpctx)
mpctx->video_next_pts = MP_NOPTS_VALUE;
mpctx->total_avsync_change = 0;
mpctx->last_av_difference = 0;
+ mpctx->display_sync_disable_counter = 0;
mpctx->dropped_frames_total = 0;
mpctx->dropped_frames = 0;
mpctx->drop_message_shown = 0;
+ mpctx->display_sync_drift_dir = 0;
mpctx->video_status = mpctx->d_video ? STATUS_SYNCING : STATUS_EOF;
}
@@ -606,8 +610,9 @@ static int get_req_frames(struct MPContext *mpctx, bool eof)
if (eof || mpctx->video_pts == MP_NOPTS_VALUE)
return 1;
+ int min = 2 + (VS_IS_DISP(mpctx->opts->video_sync) ? 1 : 0);
int req = vo_get_num_req_frames(mpctx->video_out);
- return MPCLAMP(req, 2, MP_ARRAY_SIZE(mpctx->next_frames));
+ return MPCLAMP(req, min, MP_ARRAY_SIZE(mpctx->next_frames));
}
// Whether it's fine to call add_new_frame() now.
@@ -705,6 +710,8 @@ static void update_avsync_before_frame(struct MPContext *mpctx)
if (!mpctx->sync_audio_to_video || mpctx->video_status < STATUS_READY) {
mpctx->time_frame = 0;
+ } else if (mpctx->display_sync_active || opts->video_sync == VS_NONE) {
+ // don't touch the timing
} else if (mpctx->audio_status == STATUS_PLAYING &&
mpctx->video_status == STATUS_PLAYING &&
!ao_untimed(mpctx->ao))
@@ -856,6 +863,196 @@ fail:
return require_exact ? -1 : total_duration / num;
}
+static bool using_spdif_passthrough(struct MPContext *mpctx)
+{
+ if (mpctx->d_audio && mpctx->d_audio->afilter)
+ return !af_fmt_is_pcm(mpctx->d_audio->afilter->output.format);
+ return false;
+}
+
+// Find a speed factor such that the display FPS is an integer multiple of the
+// effective video FPS. If this is not possible, try to do it for multiples,
+// which still leads to an improved end result.
+// Both parameters are durations in seconds.
+static double calc_best_speed(struct MPContext *mpctx, double vsync, double frame)
+{
+ struct MPOpts *opts = mpctx->opts;
+
+ double ratio = frame / vsync;
+ for (int factor = 1; factor <= 5; factor++) {
+ double scale = ratio * factor / floor(ratio * factor + 0.5);
+ if (fabs(scale - 1) > opts->sync_max_video_change / 100)
+ continue; // large deviation, skip
+ return scale; // decent match found
+ }
+ return -1;
+}
+
+// Manipulate frame timing for display sync, or do nothing for normal timing.
+static void handle_display_sync_frame(struct MPContext *mpctx,
+ struct vo_frame *frame)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct vo *vo = mpctx->video_out;
+ bool old_display_sync = mpctx->display_sync_active;
+ int mode = opts->video_sync;
+
+ if (!mpctx->display_sync_active) {
+ mpctx->display_sync_error = 0.0;
+ mpctx->display_sync_drift_dir = 0;
+ }
+
+ mpctx->display_sync_active = false;
+ mpctx->speed_factor_a = 1.0;
+ mpctx->speed_factor_v = 1.0;
+
+ if (!VS_IS_DISP(mode))
+ goto done;
+ bool resample = mode == VS_DISP_RESAMPLE || mode == VS_DISP_RESAMPLE_VDROP ||
+ mode == VS_DISP_RESAMPLE_NONE;
+ bool drop = mode == VS_DISP_VDROP || mode == VS_DISP_RESAMPLE ||
+ mode == VS_DISP_RESAMPLE_VDROP;
+ drop &= (opts->frame_dropping & 1);
+
+ if (resample && using_spdif_passthrough(mpctx))
+ goto done;
+
+ double vsync = vo_get_vsync_interval(vo) / 1e6;
+ if (vsync <= 0)
+ goto done;
+
+ double adjusted_duration = stabilize_frame_duration(mpctx, true);
+ if (adjusted_duration >= 0)
+ adjusted_duration /= opts->playback_speed;
+ if (adjusted_duration <= 0.002 || adjusted_duration > 0.05)
+ goto done;
+
+ double prev_duration = mpctx->display_sync_frameduration;
+ mpctx->display_sync_frameduration = adjusted_duration;
+ if (adjusted_duration != prev_duration) {
+ mpctx->display_sync_disable_counter = 50;
+ goto done;
+ }
+
+ double video_speed_correction = calc_best_speed(mpctx, vsync, adjusted_duration);
+ if (video_speed_correction <= 0)
+ goto done;
+
+ double av_diff = mpctx->last_av_difference;
+ if (fabs(av_diff) > 0.5)
+ goto done;
+
+ // At this point, we decided that we could use display sync for this frame.
+ // But if we switch too often between these modes, keep it disabled. In
+ // fact, we disable it if it just wants to switch between enable/disable
+ // more than once in the last N frames.
+ if (!old_display_sync) {
+ if (mpctx->display_sync_disable_counter > 0)
+ goto done; // keep disabled
+ mpctx->display_sync_disable_counter = 50;
+ }
+
+ MP_STATS(mpctx, "value %f avdiff", av_diff);
+
+ // Intended number of additional display frames to drop (<0) or repeat (>0)
+ int drop_repeat = 0;
+
+ // If we are too far ahead/behind, attempt to drop/repeat frames. In
+ // particular, don't attempt to change speed for them.
+ if (drop) {
+ drop_repeat = -av_diff / vsync; // round towards 0
+ av_diff -= drop_repeat * vsync;
+ }
+
+ if (resample) {
+ double audio_factor = 1.0;
+ if (mode == VS_DISP_RESAMPLE && mpctx->audio_status == STATUS_PLAYING) {
+ // Try to smooth out audio timing drifts. This can happen if either
+ // video isn't playing at expected speed, or audio is not playing at
+ // the requested speed. Both are unavoidable.
+ // The audio desync is made up of 2 parts: 1. drift due to rounding
+ // errors and imperfect information, and 2. an offset, due to
+ // unaligned audio/video start, or disruptive events halting audio
+ // or video for a small time.
+ // Instead of trying to be clever, just apply an awfully dumb drift
+ // compensation with a constant factor, which does what we want. In
+ // theory we could calculate the exact drift compensation needed,
+ // but it likely would be wrong anyway, and we'd run into the same
+ // issues again, except with more complex code.
+ // 1 means drifts to positive, -1 means drifts to negative
+ double max_drift = vsync / 2;
+ int new = mpctx->display_sync_drift_dir;
+ if (av_diff * -mpctx->display_sync_drift_dir >= 0)
+ new = 0;
+ if (fabs(av_diff) > max_drift)
+ new = copysign(1, av_diff);
+ if (mpctx->display_sync_drift_dir != new) {
+ MP_VERBOSE(mpctx, "Change display sync audio drift: %d\n", new);
+ mpctx->display_sync_drift_dir = new;
+ }
+ double max_correct = opts->sync_max_audio_change / 100;
+ audio_factor = 1 + max_correct * -mpctx->display_sync_drift_dir;
+ }
+
+ mpctx->speed_factor_a = audio_factor * video_speed_correction;
+
+ MP_STATS(mpctx, "value %f aspeed", mpctx->speed_factor_a - 1);
+ }
+
+ // Determine for how many vsyncs a frame should be displayed. This can be
+ // e.g. 2 for 30hz on a 60hz display. It can also be 0 if the video
+ // framerate is higher than the display framerate.
+ // We use the speed-adjusted (i.e. real) frame duration for this.
+ double frame_duration = adjusted_duration / video_speed_correction;
+ double ratio = (frame_duration + mpctx->display_sync_error) / vsync;
+ int num_vsyncs = MPMAX(floor(ratio + 0.5), 0);
+ mpctx->display_sync_error += frame_duration - num_vsyncs * vsync;
+ frame->vsync_offset = mpctx->display_sync_error * 1e6;
+
+ MP_DBG(mpctx, "s=%f vsyncs=%d dur=%f ratio=%f err=%.20f (%f)\n",
+ video_speed_correction, num_vsyncs, adjusted_duration, ratio,
+ mpctx->display_sync_error, mpctx->display_sync_error / vsync);
+
+ // We can only drop all frames at most. We can repeat much more frames,
+ // but we still limit it to 10 times the original frames to avoid that
+ // corner cases or exceptional situations cause too much havoc.
+ drop_repeat = MPCLAMP(drop_repeat, -num_vsyncs, num_vsyncs * 10);
+ num_vsyncs += drop_repeat;
+ if (drop_repeat < 0)
+ vo_increment_drop_count(vo, 1);
+
+ // Estimate the video position, so we can calculate a good A/V difference
+ // value with update_avsync_after_frame() later. This is used to estimate
+ // A/V drift.
+ mpctx->time_frame = 0;
+ double time_left = (vo_get_next_frame_start_time(vo) - mp_time_us()) / 1e6;
+ if (time_left >= 0)
+ mpctx->time_frame += time_left;
+ // We also know that the timing is (necessarily) off, because we have to
+ // align frame timings on the vsync boundaries. This is unavoidable, and
+ // for the sake of the video sync calculations we pretend it's perfect.
+ mpctx->time_frame -= mpctx->display_sync_error;
+
+ mpctx->speed_factor_v = video_speed_correction;
+
+ frame->num_vsyncs = num_vsyncs;
+ frame->display_synced = true;
+
+ mpctx->display_sync_active = true;
+
+done:
+
+ update_playback_speed(mpctx);
+
+ if (old_display_sync != mpctx->display_sync_active) {
+ MP_VERBOSE(mpctx, "Video sync mode %s.\n",
+ mpctx->display_sync_active ? "enabled" : "disabled");
+ }
+
+ mpctx->display_sync_disable_counter =
+ MPMAX(0, mpctx->display_sync_disable_counter - 1);
+}
+
// Return the next frame duration as stored in the file.
// frame=0 means the current frame, 1 the frame after that etc.
// Can return -1, though usually will return a fallback if frame unavailable.
@@ -949,7 +1146,9 @@ void write_video(struct MPContext *mpctx, double endpts)
int64_t pts = mp_time_us() + (int64_t)(time_frame * 1e6);
// wait until VO wakes us up to get more frames
- if (!vo_is_ready_for_frame(vo, pts)) {
+ // (NB: in theory, the 1st frame after display sync mode change uses the
+ // wrong waiting mode)
+ if (!vo_is_ready_for_frame(vo, mpctx->display_sync_active ? -1 : pts)) {
if (video_feed_async_filter(mpctx) < 0)
goto error;
return;
@@ -960,6 +1159,7 @@ void write_video(struct MPContext *mpctx, double endpts)
.pts = pts,
.duration = -1,
.num_frames = mpctx->num_next_frames,
+ .num_vsyncs = 1,
};
for (int n = 0; n < dummy.num_frames; n++)
dummy.frames[n] = mpctx->next_frames[n];
@@ -974,6 +1174,8 @@ void write_video(struct MPContext *mpctx, double endpts)
frame->duration = MPCLAMP(diff, 0, 10) * 1e6;
}
+ handle_display_sync_frame(mpctx, frame);
+
mpctx->video_pts = mpctx->next_frames[0]->pts;
mpctx->last_vo_pts = mpctx->video_pts;
mpctx->playback_pts = mpctx->video_pts;