summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2015-11-13 22:45:40 +0100
committerwm4 <wm4@nowhere>2015-11-13 22:45:40 +0100
commitd32c4c75ef1cf4d69474a0a0e8f8127e77910099 (patch)
tree355fdc7b230f4b544dfb5d23a8d174a347c793fb
parent624c9e46ce0bb4f547ffaf9cd6541700cea96ddb (diff)
downloadmpv-d32c4c75ef1cf4d69474a0a0e8f8127e77910099.tar.bz2
mpv-d32c4c75ef1cf4d69474a0a0e8f8127e77910099.tar.xz
player: refactor display-sync frame duration calculations
Get rid of get_past_frame_durations(), which was a bit too messy. Add a past_frames array, which contains the same information in a more reasonable way. This also means that we can get the exact current and past frame durations without going through awful stuff. (The main problem is that vo_pts_history contains future frames as well, which is needed for frame backstepping etc., but gets in the way here.) Also disable the automatic disabling of display-sync if the frame duration changes, and extend the frame durations allowed for display sync. To allow arbitrarily high durations, vo.c needs to be changed to pause and potentially redraw OSD while showing a single frame, so they're still limited. In an attempt to deal with VFR, calculate the overall speed using the average FPS. The frame scheduling itself does not use the average FPS, but the duration of the current frame. This does not work too well, but provides a good base for further improvements. Where this commit actually helps a lot is dealing with rounded timestamps, e.g. if the container framerate is wrong or unknown, or if the muxer wrote incorrectly rounded timestamps. While the rounding errors apparently can't be get rid of completely in the general case, this is still much better than e.g. disabling display-sync completely just because some frame durations go out of bounds.
-rw-r--r--player/command.c6
-rw-r--r--player/core.h18
-rw-r--r--player/loadfile.c1
-rw-r--r--player/playloop.c21
-rw-r--r--player/video.c186
5 files changed, 98 insertions, 134 deletions
diff --git a/player/command.c b/player/command.c
index db368df079..e3b8feab98 100644
--- a/player/command.c
+++ b/player/command.c
@@ -2687,10 +2687,10 @@ static int mp_property_vf_fps(void *ctx, struct m_property *prop,
MPContext *mpctx = ctx;
if (!mpctx->d_video)
return M_PROPERTY_UNAVAILABLE;
- double res = stabilize_frame_duration(mpctx, false);
- if (res <= 0)
+ double avg = calc_average_frame_duration(mpctx);
+ if (avg <= 0)
return M_PROPERTY_UNAVAILABLE;
- return m_property_double_ro(action, arg, 1 / res);
+ return m_property_double_ro(action, arg, 1.0 / avg);
}
/// Video aspect (RO)
diff --git a/player/core.h b/player/core.h
index 80bd08266c..fcdf0e3595 100644
--- a/player/core.h
+++ b/player/core.h
@@ -76,9 +76,6 @@ enum seek_precision {
MPSEEK_VERY_EXACT,
};
-// 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,
@@ -97,6 +94,13 @@ enum video_sync {
(x) == VS_DISP_VDROP || \
(x) == VS_DISP_NONE)
+// Information about past video frames that have been sent to the VO.
+struct frame_info {
+ double pts;
+ double duration; // PTS difference to next frame
+ double approx_duration; // possibly fixed/smoothed out duration
+};
+
struct track {
enum stream_type type;
@@ -264,7 +268,6 @@ typedef struct MPContext {
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;
@@ -321,6 +324,10 @@ typedef struct MPContext {
uint64_t vo_pts_history_seek_ts;
uint64_t backstep_start_seek_ts;
bool backstep_active;
+ // Past timestamps etc. (stupidly duplicated with vo_pts_history).
+ // The newest frame is at index 0.
+ struct frame_info *past_frames;
+ int num_past_frames;
double next_heartbeat;
double last_idle_tick;
@@ -498,7 +505,6 @@ void mp_idle(struct MPContext *mpctx);
void idle_loop(struct MPContext *mpctx);
int handle_force_window(struct MPContext *mpctx, bool force);
void add_frame_pts(struct MPContext *mpctx, double pts);
-int get_past_frame_durations(struct MPContext *mpctx, double *fd, int num);
void seek_to_last_frame(struct MPContext *mpctx);
// scripting.c
@@ -528,6 +534,6 @@ void write_video(struct MPContext *mpctx, double endpts);
void mp_force_video_refresh(struct MPContext *mpctx);
void uninit_video_out(struct MPContext *mpctx);
void uninit_video_chain(struct MPContext *mpctx);
-double stabilize_frame_duration(struct MPContext *mpctx, bool require_exact);
+double calc_average_frame_duration(struct MPContext *mpctx);
#endif /* MPLAYER_MP_CORE_H */
diff --git a/player/loadfile.c b/player/loadfile.c
index c472cad344..d72f13da61 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -1034,7 +1034,6 @@ 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;
diff --git a/player/playloop.c b/player/playloop.c
index f5463e2622..643f290c48 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -693,27 +693,6 @@ void add_frame_pts(struct MPContext *mpctx, double pts)
mpctx->vo_pts_history_pts[0] = pts;
}
-// Return the last (at most num) frame duration in fd[]. Return the number of
-// entries written to fd[] (range [0, num]). fd[0] is the most recent frame.
-int get_past_frame_durations(struct MPContext *mpctx, double *fd, int num)
-{
- double next_pts = mpctx->vo_pts_history_pts[0];
- if (mpctx->vo_pts_history_seek[0] != mpctx->vo_pts_history_seek_ts ||
- next_pts == MP_NOPTS_VALUE)
- return 0;
- int num_ret = 0;
- for (int n = 1; n < MAX_NUM_VO_PTS && num_ret < num; n++) {
- double frame_pts = mpctx->vo_pts_history_pts[n];
- // Discontinuity -> refuse to return a value.
- if (mpctx->vo_pts_history_seek[n] != mpctx->vo_pts_history_seek_ts ||
- next_pts <= frame_pts || frame_pts == MP_NOPTS_VALUE)
- break;
- fd[num_ret++] = next_pts - frame_pts;
- next_pts = frame_pts;
- }
- return num_ret;
-}
-
static double find_previous_pts(struct MPContext *mpctx, double pts)
{
for (int n = 0; n < MAX_NUM_VO_PTS - 1; n++) {
diff --git a/player/video.c b/player/video.c
index 430fed530e..398d74a643 100644
--- a/player/video.c
+++ b/player/video.c
@@ -206,6 +206,7 @@ void reset_video_state(struct MPContext *mpctx)
mpctx->time_frame = 0;
mpctx->video_pts = MP_NOPTS_VALUE;
mpctx->video_next_pts = MP_NOPTS_VALUE;
+ mpctx->num_past_frames = 0;
mpctx->total_avsync_change = 0;
mpctx->last_av_difference = 0;
mpctx->display_sync_disable_counter = 0;
@@ -799,76 +800,18 @@ static void init_vo(struct MPContext *mpctx)
mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL);
}
-// Attempt to stabilize frame duration from jittery timestamps. This is mostly
-// needed with semi-broken file formats which round timestamps to ms, or files
-// created from them.
-// We do this to make a stable decision how much to change video playback speed.
-// Otherwise calc_best_speed() could make a different decision every frame, and
-// also audio speed would have to be readjusted all the time.
-// Return -1 if the frame duration seems to be unstable.
-// If require_exact is false, just return the average frame duration on failure.
-double stabilize_frame_duration(struct MPContext *mpctx, bool require_exact)
+double calc_average_frame_duration(struct MPContext *mpctx)
{
- if (require_exact && mpctx->broken_fps_header)
- return -1;
-
- // Note: the past frame durations are raw and unadjusted.
- double fd[10];
- int num = get_past_frame_durations(mpctx, fd, MP_ARRAY_SIZE(fd));
- if (num < MP_ARRAY_SIZE(fd))
- return -1;
-
- bool ok = true;
- double min = fd[0];
- double max = fd[0];
- double total_duration = 0;
- for (int n = 0; n < num; n++) {
- double cur = fd[n];
- if (fabs(cur - fd[num - 1]) > FRAME_DURATION_TOLERANCE)
- ok = false;
- min = MPMIN(min, cur);
- max = MPMAX(max, cur);
- total_duration += cur;
- }
-
- if (max - min > FRAME_DURATION_TOLERANCE || !ok)
- goto fail;
-
- // It's not really possible to compute the actual, correct FPS, unless we
- // e.g. consider a list of potentially correct values, detect cycles, or
- // use similar guessing methods.
- // Naively using the average between min and max should give a stable, but
- // still relatively close value.
- double modified_duration = (min + max) / 2;
-
- // Except for the demuxer reported FPS, which might be the correct one.
- // VFR files could contain segments that don't match.
- if (mpctx->d_video->fps > 0) {
- double demux_duration = 1.0 / mpctx->d_video->fps;
- if (fabs(modified_duration - demux_duration) <= FRAME_DURATION_TOLERANCE)
- modified_duration = demux_duration;
- }
-
- // Verify the estimated stabilized frame duration with the actual time
- // passed in these frames. If it's wrong (wrong FPS in the header), then
- // this will deviate a bit.
- if (fabs(total_duration - modified_duration * num) > FRAME_DURATION_TOLERANCE)
- {
- if (require_exact && !mpctx->broken_fps_header) {
- // The error message is slightly misleading: a framerate header
- // field is not really needed, as long as the file has an exact
- // timebase.
- MP_WARN(mpctx, "File has broken or missing framerate header\n"
- "field, or is VFR with broken timestamps.\n");
- mpctx->broken_fps_header = true;
- }
- goto fail;
+ double total = 0;
+ int num = 0;
+ for (int n = 0; n < mpctx->num_past_frames; n++) {
+ double dur = mpctx->past_frames[0].approx_duration;
+ if (dur <= 0)
+ continue;
+ total += dur;
+ num += 1;
}
-
- return modified_duration;
-
-fail:
- return require_exact ? -1 : total_duration / num;
+ return num > 0 ? total / num : 0;
}
static bool using_spdif_passthrough(struct MPContext *mpctx)
@@ -969,24 +912,17 @@ static void handle_display_sync_frame(struct MPContext *mpctx,
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;
+ double adjusted_duration = mpctx->past_frames[0].approx_duration;
+ double avg_duration = calc_average_frame_duration(mpctx);
+ adjusted_duration /= opts->playback_speed;
+ avg_duration /= opts->playback_speed;
+ if (adjusted_duration <= 0.001 || adjusted_duration > 0.5)
goto done;
- }
- mpctx->speed_factor_v = calc_best_speed(mpctx, vsync, adjusted_duration);
- if (mpctx->speed_factor_v <= 0) {
+ mpctx->speed_factor_v = calc_best_speed(mpctx, vsync, avg_duration);
+ // If it doesn't work, play at normal speed.
+ if (mpctx->speed_factor_v <= 0)
mpctx->speed_factor_v = 1.0;
- goto done;
- }
double av_diff = mpctx->last_av_difference;
if (fabs(av_diff) > 0.5)
@@ -1085,26 +1021,58 @@ static void schedule_frame(struct MPContext *mpctx, struct vo_frame *frame)
}
}
-// 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.
-static double get_frame_duration(struct MPContext *mpctx, int frame)
+// Determine the mpctx->past_frames[0] frame duration.
+static void calculate_frame_duration(struct MPContext *mpctx)
{
- struct MPOpts *opts = mpctx->opts;
- struct vo *vo = mpctx->video_out;
+ assert(mpctx->num_past_frames >= 1 && mpctx->num_next_frames >= 1);
- double diff = -1;
- if (frame + 2 <= mpctx->num_next_frames) {
- double vpts0 = mpctx->next_frames[frame]->pts;
- double vpts1 = mpctx->next_frames[frame + 1]->pts;
- if (vpts0 != MP_NOPTS_VALUE && vpts1 != MP_NOPTS_VALUE)
- diff = vpts1 - vpts0;
+ double demux_duration =
+ mpctx->d_video->fps > 0 ? 1.0 / mpctx->d_video->fps : -1;
+ double duration = -1;
+
+ if (mpctx->num_next_frames >= 2) {
+ double pts0 = mpctx->next_frames[0]->pts;
+ double pts1 = mpctx->next_frames[1]->pts;
+ if (pts0 != MP_NOPTS_VALUE && pts1 != MP_NOPTS_VALUE && pts1 >= pts0)
+ duration = pts1 - pts0;
+ } else {
+ // E.g. last frame on EOF.
+ duration = demux_duration;
}
- if (diff < 0 && mpctx->d_video->fps > 0)
- diff = 1.0 / mpctx->d_video->fps; // fallback to demuxer-reported fps
- if (opts->untimed || vo->driver->untimed)
- diff = -1; // disable frame dropping and aspects of frame timing
- return diff;
+
+ // The following code tries to compensate for rounded Matroska timestamps
+ // by "unrounding" frame durations, or if not possible, approximating them.
+ // These formats usually round on 1ms. (Some muxers do this incorrectly,
+ // and might be off by 2ms or more, and compensate for it later by an
+ // equal rounding error into the opposite direction. Don't try to deal
+ // with them; too much potential damage to timing.)
+ double tolerance = 0.0011;
+
+ double total = 0;
+ int num_dur = 0;
+ for (int n = 1; n < mpctx->num_past_frames; n++) {
+ // Eliminate likely outliers using a really dumb heuristic.
+ double dur = mpctx->past_frames[n].duration;
+ if (dur <= 0 || fabs(dur - duration) >= tolerance)
+ break;
+ total += dur;
+ num_dur += 1;
+ }
+
+ // Try if the demuxer frame rate fits - if so, just take it.
+ double approx_duration = duration;
+ if (demux_duration > 0) {
+ // Note that even if each timestamp is within rounding tolerance, it
+ // could literally not add up (e.g. if demuxer FPS is rounded itself).
+ if (fabs(duration - demux_duration) < tolerance &&
+ fabs(total - demux_duration * num_dur) < tolerance)
+ {
+ approx_duration = demux_duration;
+ }
+ }
+
+ mpctx->past_frames[0].duration = duration;
+ mpctx->past_frames[0].approx_duration = approx_duration;
}
void write_video(struct MPContext *mpctx, double endpts)
@@ -1190,6 +1158,16 @@ void write_video(struct MPContext *mpctx, double endpts)
}
assert(mpctx->num_next_frames >= 1);
+
+ if (mpctx->num_past_frames >= MAX_NUM_VO_PTS)
+ mpctx->num_past_frames--;
+ MP_TARRAY_INSERT_AT(mpctx, mpctx->past_frames, mpctx->num_past_frames, 0,
+ (struct frame_info){0});
+ struct frame_info *frame_info = &mpctx->past_frames[0];
+
+ frame_info->pts = mpctx->next_frames[0]->pts;
+ calculate_frame_duration(mpctx);
+
struct vo_frame dummy = {
.pts = pts,
.duration = -1,
@@ -1201,7 +1179,9 @@ void write_video(struct MPContext *mpctx, double endpts)
dummy.frames[n] = mpctx->next_frames[n];
struct vo_frame *frame = vo_frame_ref(&dummy);
- double diff = get_frame_duration(mpctx, 0);
+ double diff = frame_info->approx_duration;
+ if (opts->untimed || vo->driver->untimed)
+ diff = -1; // disable frame dropping and aspects of frame timing
if (diff >= 0) {
// expected A/V sync correction is ignored
diff /= mpctx->video_speed;
@@ -1214,6 +1194,8 @@ void write_video(struct MPContext *mpctx, double endpts)
mpctx->last_vo_pts = mpctx->video_pts;
mpctx->playback_pts = mpctx->video_pts;
+ shift_frames(mpctx);
+
schedule_frame(mpctx, frame);
mpctx->osd_force_update = true;
@@ -1222,8 +1204,6 @@ void write_video(struct MPContext *mpctx, double endpts)
vo_queue_frame(vo, frame);
- shift_frames(mpctx);
-
// The frames were shifted down; "initialize" the new first entry.
if (mpctx->num_next_frames >= 1)
handle_new_frame(mpctx);