summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2014-07-30 23:24:08 +0200
committerwm4 <wm4@nowhere>2014-07-30 23:29:00 +0200
commit1423bd0bfd671ffc023778839a540fffd1dd3686 (patch)
treeeb284a16d71ea8f6ecc6acb815a77ed711ef46da
parent8f2e9f1d613f686a834446c73465f0ca41156db3 (diff)
downloadmpv-1423bd0bfd671ffc023778839a540fffd1dd3686.tar.bz2
mpv-1423bd0bfd671ffc023778839a540fffd1dd3686.tar.xz
player: move video display code out of the playloop
Basically move the code from playloop.c to video.c. The new function write_video() now contains the code that was part of run_playloop(). There are no functional changes, except handling "new_frame_shown" slightly differently. This is done so that we don't need new a new MPContext field or a return value for write_video() to signal this condition. Instead, it's handled indirectly.
-rw-r--r--player/audio.c3
-rw-r--r--player/core.h16
-rw-r--r--player/playloop.c325
-rw-r--r--player/video.c324
4 files changed, 336 insertions, 332 deletions
diff --git a/player/audio.c b/player/audio.c
index 2491051a54..849944cf91 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -361,7 +361,8 @@ void fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
struct MPOpts *opts = mpctx->opts;
struct dec_audio *d_audio = mpctx->d_audio;
- assert(d_audio);
+ if (!d_audio)
+ return;
if (!d_audio->afilter || !mpctx->ao) {
// Probe the initial audio format. Returns AD_OK (and does nothing) if
diff --git a/player/core.h b/player/core.h
index c24a6c1f94..6b3cf55f8b 100644
--- a/player/core.h
+++ b/player/core.h
@@ -88,6 +88,8 @@ enum {
MAX_TERM_OSD_LEVEL = 1,
OSD_LEVEL_INVISIBLE = 4,
OSD_BAR_SEEK = 256,
+
+ MAX_NUM_VO_PTS = 100,
};
enum seek_type {
@@ -138,17 +140,6 @@ struct track {
bool preloaded;
};
-enum {
- MAX_NUM_VO_PTS = 100,
-
- // update_video() - code also uses: <0 error, 0 eof, >0 progress
- VD_ERROR = -1,
- VD_EOF = 0, // end of file - no new output
- VD_PROGRESS = 1, // progress, but no output; repeat call with no waiting
- VD_NEW_FRAME = 2, // the call produced a new frame
- VD_WAIT = 3, // no EOF, but no output; wait until wakeup
-};
-
/* Note that playback can be paused, stopped, etc. at any time. While paused,
* playback restart is still active, because you want seeking to work even
* if paused.
@@ -501,8 +492,7 @@ void build_cue_timeline(struct MPContext *mpctx);
void reset_video_state(struct MPContext *mpctx);
int reinit_video_chain(struct MPContext *mpctx);
int reinit_video_filters(struct MPContext *mpctx);
-int update_video(struct MPContext *mpctx, double endpts, bool reconfig_ok,
- double *frame_duration);
+void write_video(struct MPContext *mpctx, double endpts);
void mp_force_video_refresh(struct MPContext *mpctx);
void update_fps(struct MPContext *mpctx);
diff --git a/player/playloop.c b/player/playloop.c
index 60caf39389..9e4f804bc3 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -49,33 +49,8 @@
#include "video/out/vo.h"
#include "core.h"
-#include "screenshot.h"
#include "command.h"
-static const char av_desync_help_text[] =
-"\n\n"
-" *************************************************\n"
-" **** Audio/Video desynchronisation detected! ****\n"
-" *************************************************\n\n"
-"This means either the audio or the video is played too slowly.\n"
-"Possible reasons, problems, workarounds:\n"
-"- Your system is simply too slow for this file.\n"
-" Transcode it to a lower bitrate file with tools like HandBrake.\n"
-"- Broken/buggy _audio_ driver.\n"
-" Experiment with different values for --autosync, 30 is a good start.\n"
-" If you have PulseAudio, try --ao=alsa .\n"
-"- Slow video output.\n"
-" Try a different --vo driver (--vo=help for a list) or try --framedrop!\n"
-"- Playing a video file with --vo=opengl with higher FPS than the monitor.\n"
-" This is due to vsync limiting the framerate.\n"
-"- Playing from a slow network source.\n"
-" Download the file instead.\n"
-"- Try to find out whether audio/video/subs are causing this by experimenting\n"
-" with --no-video, --no-audio, or --no-sub.\n"
-"- If you swiched audio or video tracks, try seeking to force synchronization.\n"
-"If none of this helps you, file a bug report.\n\n";
-
-
void pause_player(struct MPContext *mpctx)
{
mpctx->opts->pause = 1;
@@ -528,61 +503,6 @@ bool mp_seek_chapter(struct MPContext *mpctx, int chapter)
return true;
}
-static void update_avsync(struct MPContext *mpctx)
-{
- if (mpctx->audio_status != STATUS_PLAYING ||
- mpctx->video_status != STATUS_PLAYING)
- return;
-
- double a_pos = playing_audio_pts(mpctx);
-
- mpctx->last_av_difference = a_pos - mpctx->video_pts + mpctx->audio_delay;
- if (mpctx->time_frame > 0)
- mpctx->last_av_difference +=
- mpctx->time_frame * mpctx->opts->playback_speed;
- if (a_pos == MP_NOPTS_VALUE || mpctx->video_pts == MP_NOPTS_VALUE)
- mpctx->last_av_difference = MP_NOPTS_VALUE;
- if (mpctx->last_av_difference > 0.5 && mpctx->drop_frame_cnt > 50
- && !mpctx->drop_message_shown) {
- MP_WARN(mpctx, "%s", av_desync_help_text);
- mpctx->drop_message_shown = true;
- }
-}
-
-/* Modify video timing to match the audio timeline. There are two main
- * reasons this is needed. First, video and audio can start from different
- * positions at beginning of file or after a seek (MPlayer starts both
- * immediately even if they have different pts). Second, the file can have
- * audio timestamps that are inconsistent with the duration of the audio
- * packets, for example two consecutive timestamp values differing by
- * one second but only a packet with enough samples for half a second
- * of playback between them.
- */
-static void adjust_sync(struct MPContext *mpctx, double frame_time)
-{
- struct MPOpts *opts = mpctx->opts;
-
- if (mpctx->audio_status != STATUS_PLAYING)
- return;
-
- double a_pts = written_audio_pts(mpctx) - mpctx->delay;
- double v_pts = mpctx->video_next_pts;
- double av_delay = a_pts - v_pts;
- // Try to sync vo_flip() so it will *finish* at given time
- av_delay += mpctx->last_vo_flip_duration;
- av_delay += mpctx->audio_delay; // This much pts difference is desired
-
- double change = av_delay * 0.1;
- double max_change = opts->default_max_pts_correction >= 0 ?
- opts->default_max_pts_correction : frame_time * 0.1;
- if (change < -max_change)
- change = -max_change;
- else if (change > max_change)
- change = max_change;
- mpctx->delay += change;
- mpctx->total_avsync_change += change;
-}
-
static bool handle_osd_redraw(struct MPContext *mpctx)
{
if (!mpctx->video_out || !mpctx->video_out->config_ok)
@@ -626,7 +546,7 @@ static void handle_pause_on_low_cache(struct MPContext *mpctx)
static void handle_heartbeat_cmd(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
- if (opts->heartbeat_cmd && !mpctx->paused) {
+ if (opts->heartbeat_cmd && !mpctx->paused && mpctx->video_out) {
double now = mp_time_sec();
if (mpctx->next_heartbeat <= now) {
mpctx->next_heartbeat = now + opts->heartbeat_interval;
@@ -846,25 +766,6 @@ void handle_force_window(struct MPContext *mpctx, bool reconfig)
}
}
-static double timing_sleep(struct MPContext *mpctx, double time_frame)
-{
- // assume kernel HZ=100 for softsleep, works with larger HZ but with
- // unnecessarily high CPU usage
- struct MPOpts *opts = mpctx->opts;
- double margin = opts->softsleep ? 0.011 : 0;
- while (time_frame > margin) {
- mp_sleep_us(1000000 * (time_frame - margin));
- time_frame -= get_relative_time(mpctx);
- }
- if (opts->softsleep) {
- if (time_frame < 0)
- MP_WARN(mpctx, "Warning! Softsleep underflow!\n");
- while (time_frame > 0)
- time_frame -= get_relative_time(mpctx); // burn the CPU
- }
- return time_frame;
-}
-
static double get_wakeup_period(struct MPContext *mpctx)
{
double sleeptime = 100.0; // infinite for all practical purposes
@@ -886,7 +787,7 @@ void run_playloop(struct MPContext *mpctx)
struct MPOpts *opts = mpctx->opts;
double endpts = get_play_end_pts(mpctx);
bool end_is_chapter = false;
- bool new_frame_shown = false;
+ int64_t shown_vframes = mpctx->shown_vframes;
#if HAVE_ENCODING
if (encode_lavc_didfail(mpctx->encode_lavc_ctx)) {
@@ -911,223 +812,14 @@ void run_playloop(struct MPContext *mpctx)
endpts = end;
}
- if (mpctx->d_audio)
- fill_audio_out_buffers(mpctx, endpts);
-
- if (mpctx->video_out) {
+ if (mpctx->video_out)
vo_check_events(mpctx->video_out);
- handle_cursor_autohide(mpctx);
- handle_heartbeat_cmd(mpctx);
- }
-
- while (mpctx->d_video) { // never loops, for "break;" only
- struct vo *vo = mpctx->video_out;
- update_fps(mpctx);
-
- // Whether there's still at least 1 video frame that can be shown.
- // If false, it means we can reconfig the VO if needed (normally, this
- // would disrupt playback, so only do it on !still_playing).
- bool still_playing = vo_has_next_frame(vo, true);
- // For the last frame case (frame is being displayed).
- still_playing |= mpctx->playing_last_frame;
- still_playing |= mpctx->last_frame_duration > 0;
-
- double frame_time = 0;
- int r = update_video(mpctx, endpts, !still_playing, &frame_time);
- MP_TRACE(mpctx, "update_video: %d (still_playing=%d)\n", r, still_playing);
-
- if (r == VD_WAIT) // Demuxer will wake us up for more packets to decode.
- break;
-
- if (r < 0) {
- MP_FATAL(mpctx, "Could not initialize video chain.\n");
- int uninit = INITIALIZED_VCODEC;
- if (!opts->force_vo)
- uninit |= INITIALIZED_VO;
- uninit_player(mpctx, uninit);
- if (!mpctx->current_track[STREAM_AUDIO])
- mpctx->stop_play = PT_NEXT_ENTRY;
- mpctx->error_playing = true;
- handle_force_window(mpctx, true);
- return; // restart loop
- }
-
- if (r == VD_EOF) {
- if (!mpctx->playing_last_frame && mpctx->last_frame_duration > 0) {
- mpctx->time_frame += mpctx->last_frame_duration;
- mpctx->last_frame_duration = 0;
- mpctx->playing_last_frame = true;
- MP_VERBOSE(mpctx, "showing last frame\n");
- }
- }
- if (r == VD_NEW_FRAME) {
- MP_TRACE(mpctx, "frametime=%5.3f\n", frame_time);
+ handle_cursor_autohide(mpctx);
+ handle_heartbeat_cmd(mpctx);
- if (mpctx->video_status > STATUS_PLAYING)
- mpctx->video_status = STATUS_PLAYING;
-
- if (mpctx->video_status >= STATUS_READY) {
- mpctx->time_frame += frame_time / opts->playback_speed;
- adjust_sync(mpctx, frame_time);
- }
- } else if (r == VD_EOF && mpctx->playing_last_frame) {
- // Let video timing code continue displaying.
- mpctx->video_status = STATUS_DRAINING;
- MP_VERBOSE(mpctx, "still showing last frame\n");
- } else if (r <= 0) {
- // EOF or error
- mpctx->delay = 0;
- mpctx->last_av_difference = 0;
- mpctx->video_status = STATUS_EOF;
- MP_VERBOSE(mpctx, "video EOF\n");
- } else {
- if (mpctx->video_status > STATUS_PLAYING)
- mpctx->video_status = STATUS_PLAYING;
-
- // Decode more in next iteration.
- mpctx->sleeptime = 0;
- MP_TRACE(mpctx, "filtering more video\n");
- }
-
- // Actual playback starts when both audio and video are ready.
- if (mpctx->video_status == STATUS_READY)
- break;
-
- if (mpctx->paused && mpctx->video_status >= STATUS_READY)
- break;
-
- mpctx->time_frame -= get_relative_time(mpctx);
- double audio_pts = playing_audio_pts(mpctx);
- if (!mpctx->sync_audio_to_video || mpctx->video_status < STATUS_READY) {
- mpctx->time_frame = 0;
- } else if (mpctx->audio_status == STATUS_PLAYING &&
- mpctx->video_status == STATUS_PLAYING)
- {
- double buffered_audio = ao_get_delay(mpctx->ao);
- MP_TRACE(mpctx, "audio delay=%f\n", buffered_audio);
-
- if (opts->autosync) {
- /* Smooth reported playback position from AO by averaging
- * it with the value expected based on previus value and
- * time elapsed since then. May help smooth video timing
- * with audio output that have inaccurate position reporting.
- * This is badly implemented; the behavior of the smoothing
- * now undesirably depends on how often this code runs
- * (mainly depends on video frame rate). */
- float predicted = (mpctx->delay / opts->playback_speed +
- mpctx->time_frame);
- float difference = buffered_audio - predicted;
- buffered_audio = predicted + difference / opts->autosync;
- }
-
- mpctx->time_frame = (buffered_audio -
- mpctx->delay / opts->playback_speed);
- } else {
- /* If we're more than 200 ms behind the right playback
- * position, don't try to speed up display of following
- * frames to catch up; continue with default speed from
- * the current frame instead.
- * If untimed is set always output frames immediately
- * without sleeping.
- */
- if (mpctx->time_frame < -0.2 || opts->untimed || vo->untimed)
- mpctx->time_frame = 0;
- }
-
- double vsleep = mpctx->time_frame - vo->flip_queue_offset;
- if (vsleep > 0.050) {
- mpctx->sleeptime = MPMIN(mpctx->sleeptime, vsleep - 0.040);
- break;
- }
- mpctx->sleeptime = 0;
- mpctx->playing_last_frame = false;
-
- // last frame case
- if (r != VD_NEW_FRAME)
- break;
-
- //=================== FLIP PAGE (VIDEO BLT): ======================
-
-
- mpctx->video_pts = mpctx->video_next_pts;
- mpctx->last_vo_pts = mpctx->video_pts;
- mpctx->playback_pts = mpctx->video_pts;
-
- update_subtitles(mpctx);
- update_osd_msg(mpctx);
-
- MP_STATS(mpctx, "vo draw frame");
-
- vo_new_frame_imminent(vo);
-
- MP_STATS(mpctx, "vo sleep");
-
- mpctx->time_frame -= get_relative_time(mpctx);
- mpctx->time_frame -= vo->flip_queue_offset;
- if (mpctx->time_frame > 0.001)
- mpctx->time_frame = timing_sleep(mpctx, mpctx->time_frame);
- mpctx->time_frame += vo->flip_queue_offset;
-
- int64_t t2 = mp_time_us();
- /* Playing with playback speed it's possible to get pathological
- * cases with mpctx->time_frame negative enough to cause an
- * overflow in pts_us calculation, thus the MPMAX. */
- double time_frame = MPMAX(mpctx->time_frame, -1);
- int64_t pts_us = mpctx->last_time + time_frame * 1e6;
- int duration = -1;
- double pts2 = vo_get_next_pts(vo, 0); // this is the next frame PTS
- if (mpctx->video_pts != MP_NOPTS_VALUE && pts2 == MP_NOPTS_VALUE) {
- // Make up a frame duration. Using the frame rate is not a good
- // choice, since the frame rate could be unset/broken/random.
- float fps = mpctx->d_video->fps;
- double frame_duration = fps > 0 ? 1.0 / fps : 0;
- pts2 = mpctx->video_pts + MPCLAMP(frame_duration, 0.0, 5.0);
- }
- if (pts2 != MP_NOPTS_VALUE) {
- // expected A/V sync correction is ignored
- double diff = (pts2 - mpctx->video_pts);
- diff /= opts->playback_speed;
- if (mpctx->time_frame < 0)
- diff += mpctx->time_frame;
- if (diff < 0)
- diff = 0;
- if (diff > 10)
- diff = 10;
- duration = diff * 1e6;
- mpctx->last_frame_duration = diff;
- }
- if (mpctx->video_status != STATUS_PLAYING)
- duration = -1;
-
- MP_STATS(mpctx, "start flip");
- vo_flip_page(vo, pts_us | 1, duration);
- MP_STATS(mpctx, "end flip");
-
- if (audio_pts != MP_NOPTS_VALUE)
- MP_STATS(mpctx, "value %f ptsdiff", mpctx->video_pts - audio_pts);
-
- mpctx->last_vo_flip_duration = (mp_time_us() - t2) * 0.000001;
- if (vo->driver->flip_page_timed) {
- // No need to adjust sync based on flip speed
- mpctx->last_vo_flip_duration = 0;
- // For print_status - VO call finishing early is OK for sync
- mpctx->time_frame -= get_relative_time(mpctx);
- }
- mpctx->shown_vframes++;
- if (mpctx->video_status < STATUS_PLAYING)
- mpctx->video_status = STATUS_READY;
- update_avsync(mpctx);
- screenshot_flip(mpctx);
- new_frame_shown = true;
-
- mp_notify(mpctx, MPV_EVENT_TICK, NULL);
-
- if (!mpctx->sync_audio_to_video)
- mpctx->video_status = STATUS_EOF;
-
- break;
- } // video
+ fill_audio_out_buffers(mpctx, endpts);
+ write_video(mpctx, endpts);
if (mpctx->video_status == STATUS_EOF || mpctx->paused) {
if (mp_time_sec() - mpctx->last_idle_tick > 0.5) {
@@ -1145,7 +837,7 @@ void run_playloop(struct MPContext *mpctx)
mpctx->video_status = STATUS_PLAYING;
get_relative_time(mpctx);
mpctx->sleeptime = 0;
- new_frame_shown = true;
+ shown_vframes -= 1; // make framestep think there was a new video frame
}
if (mpctx->audio_status == STATUS_READY)
fill_audio_out_buffers(mpctx, endpts); // actually play prepared buffer
@@ -1198,6 +890,7 @@ void run_playloop(struct MPContext *mpctx)
handle_keep_open(mpctx);
if (!mpctx->stop_play && mpctx->restart_complete) {
+ bool new_frame_shown = shown_vframes < mpctx->shown_vframes;
// If no more video is available, one frame means one playloop iteration.
// Otherwise, one frame means one video frame.
diff --git a/player/video.c b/player/video.c
index 713849798b..eea8fba124 100644
--- a/player/video.c
+++ b/player/video.c
@@ -32,6 +32,7 @@
#include "common/common.h"
#include "common/encode.h"
#include "options/m_property.h"
+#include "osdep/timer.h"
#include "audio/out/ao.h"
#include "demux/demux.h"
@@ -45,6 +46,39 @@
#include "core.h"
#include "command.h"
+#include "screenshot.h"
+
+enum {
+ // update_video() - code also uses: <0 error, 0 eof, >0 progress
+ VD_ERROR = -1,
+ VD_EOF = 0, // end of file - no new output
+ VD_PROGRESS = 1, // progress, but no output; repeat call with no waiting
+ VD_NEW_FRAME = 2, // the call produced a new frame
+ VD_WAIT = 3, // no EOF, but no output; wait until wakeup
+};
+
+static const char av_desync_help_text[] =
+"\n\n"
+" *************************************************\n"
+" **** Audio/Video desynchronisation detected! ****\n"
+" *************************************************\n\n"
+"This means either the audio or the video is played too slowly.\n"
+"Possible reasons, problems, workarounds:\n"
+"- Your system is simply too slow for this file.\n"
+" Transcode it to a lower bitrate file with tools like HandBrake.\n"
+"- Broken/buggy _audio_ driver.\n"
+" Experiment with different values for --autosync, 30 is a good start.\n"
+" If you have PulseAudio, try --ao=alsa .\n"
+"- Slow video output.\n"
+" Try a different --vo driver (--vo=help for a list) or try --framedrop!\n"
+"- Playing a video file with --vo=opengl with higher FPS than the monitor.\n"
+" This is due to vsync limiting the framerate.\n"
+"- Playing from a slow network source.\n"
+" Download the file instead.\n"
+"- Try to find out whether audio/video/subs are causing this by experimenting\n"
+" with --no-video, --no-audio, or --no-sub.\n"
+"- If you swiched audio or video tracks, try seeking to force synchronization.\n"
+"If none of this helps you, file a bug report.\n\n";
void update_fps(struct MPContext *mpctx)
{
@@ -532,8 +566,8 @@ static int video_output_image(struct MPContext *mpctx, double endpts,
}
// returns VD_* code
-int update_video(struct MPContext *mpctx, double endpts, bool reconfig_ok,
- double *frame_duration)
+static int update_video(struct MPContext *mpctx, double endpts, bool reconfig_ok,
+ double *frame_duration)
{
struct vo *video_out = mpctx->video_out;
@@ -575,3 +609,289 @@ int update_video(struct MPContext *mpctx, double endpts, bool reconfig_ok,
*frame_duration = frame_time;
return VD_NEW_FRAME;
}
+
+static double timing_sleep(struct MPContext *mpctx, double time_frame)
+{
+ // assume kernel HZ=100 for softsleep, works with larger HZ but with
+ // unnecessarily high CPU usage
+ struct MPOpts *opts = mpctx->opts;
+ double margin = opts->softsleep ? 0.011 : 0;
+ while (time_frame > margin) {
+ mp_sleep_us(1000000 * (time_frame - margin));
+ time_frame -= get_relative_time(mpctx);
+ }
+ if (opts->softsleep) {
+ if (time_frame < 0)
+ MP_WARN(mpctx, "Warning! Softsleep underflow!\n");
+ while (time_frame > 0)
+ time_frame -= get_relative_time(mpctx); // burn the CPU
+ }
+ return time_frame;
+}
+
+static void update_avsync(struct MPContext *mpctx)
+{
+ if (mpctx->audio_status != STATUS_PLAYING ||
+ mpctx->video_status != STATUS_PLAYING)
+ return;
+
+ double a_pos = playing_audio_pts(mpctx);
+
+ mpctx->last_av_difference = a_pos - mpctx->video_pts + mpctx->audio_delay;
+ if (mpctx->time_frame > 0)
+ mpctx->last_av_difference +=
+ mpctx->time_frame * mpctx->opts->playback_speed;
+ if (a_pos == MP_NOPTS_VALUE || mpctx->video_pts == MP_NOPTS_VALUE)
+ mpctx->last_av_difference = MP_NOPTS_VALUE;
+ if (mpctx->last_av_difference > 0.5 && mpctx->drop_frame_cnt > 50
+ && !mpctx->drop_message_shown) {
+ MP_WARN(mpctx, "%s", av_desync_help_text);
+ mpctx->drop_message_shown = true;
+ }
+}
+
+/* Modify video timing to match the audio timeline. There are two main
+ * reasons this is needed. First, video and audio can start from different
+ * positions at beginning of file or after a seek (MPlayer starts both
+ * immediately even if they have different pts). Second, the file can have
+ * audio timestamps that are inconsistent with the duration of the audio
+ * packets, for example two consecutive timestamp values differing by
+ * one second but only a packet with enough samples for half a second
+ * of playback between them.
+ */
+static void adjust_sync(struct MPContext *mpctx, double frame_time)
+{
+ struct MPOpts *opts = mpctx->opts;
+
+ if (mpctx->audio_status != STATUS_PLAYING)
+ return;
+
+ double a_pts = written_audio_pts(mpctx) - mpctx->delay;
+ double v_pts = mpctx->video_next_pts;
+ double av_delay = a_pts - v_pts;
+ // Try to sync vo_flip() so it will *finish* at given time
+ av_delay += mpctx->last_vo_flip_duration;
+ av_delay += mpctx->audio_delay; // This much pts difference is desired
+
+ double change = av_delay * 0.1;
+ double max_change = opts->default_max_pts_correction >= 0 ?
+ opts->default_max_pts_correction : frame_time * 0.1;
+ if (change < -max_change)
+ change = -max_change;
+ else if (change > max_change)
+ change = max_change;
+ mpctx->delay += change;
+ mpctx->total_avsync_change += change;
+}
+
+void write_video(struct MPContext *mpctx, double endpts)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct vo *vo = mpctx->video_out;
+
+ if (!mpctx->d_video)
+ return;
+
+ update_fps(mpctx);
+
+ // Whether there's still at least 1 video frame that can be shown.
+ // If false, it means we can reconfig the VO if needed (normally, this
+ // would disrupt playback, so only do it on !still_playing).
+ bool still_playing = vo_has_next_frame(vo, true);
+ // For the last frame case (frame is being displayed).
+ still_playing |= mpctx->playing_last_frame;
+ still_playing |= mpctx->last_frame_duration > 0;
+
+ double frame_time = 0;
+ int r = update_video(mpctx, endpts, !still_playing, &frame_time);
+ MP_TRACE(mpctx, "update_video: %d (still_playing=%d)\n", r, still_playing);
+
+ if (r == VD_WAIT) // Demuxer will wake us up for more packets to decode.
+ return;
+
+ if (r < 0) {
+ MP_FATAL(mpctx, "Could not initialize video chain.\n");
+ int uninit = INITIALIZED_VCODEC;
+ if (!opts->force_vo)
+ uninit |= INITIALIZED_VO;
+ uninit_player(mpctx, uninit);
+ if (!mpctx->current_track[STREAM_AUDIO])
+ mpctx->stop_play = PT_NEXT_ENTRY;
+ mpctx->error_playing = true;
+ handle_force_window(mpctx, true);
+ return; // restart loop
+ }
+
+ if (r == VD_EOF) {
+ if (!mpctx->playing_last_frame && mpctx->last_frame_duration > 0) {
+ mpctx->time_frame += mpctx->last_frame_duration;
+ mpctx->last_frame_duration = 0;
+ mpctx->playing_last_frame = true;
+ MP_VERBOSE(mpctx, "showing last frame\n");
+ }
+ }
+
+ if (r == VD_NEW_FRAME) {
+ MP_TRACE(mpctx, "frametime=%5.3f\n", frame_time);
+
+ if (mpctx->video_status > STATUS_PLAYING)
+ mpctx->video_status = STATUS_PLAYING;
+
+ if (mpctx->video_status >= STATUS_READY) {
+ mpctx->time_frame += frame_time / opts->playback_speed;
+ adjust_sync(mpctx, frame_time);
+ }
+ } else if (r == VD_EOF && mpctx->playing_last_frame) {
+ // Let video timing code continue displaying.
+ mpctx->video_status = STATUS_DRAINING;
+ MP_VERBOSE(mpctx, "still showing last frame\n");
+ } else if (r <= 0) {
+ // EOF or error
+ mpctx->delay = 0;
+ mpctx->last_av_difference = 0;
+ mpctx->video_status = STATUS_EOF;
+ MP_VERBOSE(mpctx, "video EOF\n");
+ } else {
+ if (mpctx->video_status > STATUS_PLAYING)
+ mpctx->video_status = STATUS_PLAYING;
+
+ // Decode more in next iteration.
+ mpctx->sleeptime = 0;
+ MP_TRACE(mpctx, "filtering more video\n");
+ }
+
+ // Actual playback starts when both audio and video are ready.
+ if (mpctx->video_status == STATUS_READY)
+ return;
+
+ if (mpctx->paused && mpctx->video_status >= STATUS_READY)
+ return;
+
+ mpctx->time_frame -= get_relative_time(mpctx);
+ double audio_pts = playing_audio_pts(mpctx);
+ if (!mpctx->sync_audio_to_video || mpctx->video_status < STATUS_READY) {
+ mpctx->time_frame = 0;
+ } else if (mpctx->audio_status == STATUS_PLAYING &&
+ mpctx->video_status == STATUS_PLAYING)
+ {
+ double buffered_audio = ao_get_delay(mpctx->ao);
+ MP_TRACE(mpctx, "audio delay=%f\n", buffered_audio);
+
+ if (opts->autosync) {
+ /* Smooth reported playback position from AO by averaging
+ * it with the value expected based on previus value and
+ * time elapsed since then. May help smooth video timing
+ * with audio output that have inaccurate position reporting.
+ * This is badly implemented; the behavior of the smoothing
+ * now undesirably depends on how often this code runs
+ * (mainly depends on video frame rate). */
+ float predicted = (mpctx->delay / opts->playback_speed +
+ mpctx->time_frame);
+ float difference = buffered_audio - predicted;
+ buffered_audio = predicted + difference / opts->autosync;
+ }
+
+ mpctx->time_frame = (buffered_audio -
+ mpctx->delay / opts->playback_speed);
+ } else {
+ /* If we're more than 200 ms behind the right playback
+ * position, don't try to speed up display of following
+ * frames to catch up; continue with default speed from
+ * the current frame instead.
+ * If untimed is set always output frames immediately
+ * without sleeping.
+ */
+ if (mpctx->time_frame < -0.2 || opts->untimed || vo->untimed)
+ mpctx->time_frame = 0;
+ }
+
+ double vsleep = mpctx->time_frame - vo->flip_queue_offset;
+ if (vsleep > 0.050) {
+ mpctx->sleeptime = MPMIN(mpctx->sleeptime, vsleep - 0.040);
+ return;
+ }
+ mpctx->sleeptime = 0;
+ mpctx->playing_last_frame = false;
+
+ // last frame case
+ if (r != VD_NEW_FRAME)
+ return;
+
+ //=================== FLIP PAGE (VIDEO BLT): ======================
+
+
+ mpctx->video_pts = mpctx->video_next_pts;
+ mpctx->last_vo_pts = mpctx->video_pts;
+ mpctx->playback_pts = mpctx->video_pts;
+
+ update_subtitles(mpctx);
+ update_osd_msg(mpctx);
+
+ MP_STATS(mpctx, "vo draw frame");
+
+ vo_new_frame_imminent(vo);
+
+ MP_STATS(mpctx, "vo sleep");
+
+ mpctx->time_frame -= get_relative_time(mpctx);
+ mpctx->time_frame -= vo->flip_queue_offset;
+ if (mpctx->time_frame > 0.001)
+ mpctx->time_frame = timing_sleep(mpctx, mpctx->time_frame);
+ mpctx->time_frame += vo->flip_queue_offset;
+
+ int64_t t2 = mp_time_us();
+ /* Playing with playback speed it's possible to get pathological
+ * cases with mpctx->time_frame negative enough to cause an
+ * overflow in pts_us calculation, thus the MPMAX. */
+ double time_frame = MPMAX(mpctx->time_frame, -1);
+ int64_t pts_us = mpctx->last_time + time_frame * 1e6;
+ int duration = -1;
+ double pts2 = vo_get_next_pts(vo, 0); // this is the next frame PTS
+ if (mpctx->video_pts != MP_NOPTS_VALUE && pts2 == MP_NOPTS_VALUE) {
+ // Make up a frame duration. Using the frame rate is not a good
+ // choice, since the frame rate could be unset/broken/random.
+ float fps = mpctx->d_video->fps;
+ double frame_duration = fps > 0 ? 1.0 / fps : 0;
+ pts2 = mpctx->video_pts + MPCLAMP(frame_duration, 0.0, 5.0);
+ }
+ if (pts2 != MP_NOPTS_VALUE) {
+ // expected A/V sync correction is ignored
+ double diff = (pts2 - mpctx->video_pts);
+ diff /= opts->playback_speed;
+ if (mpctx->time_frame < 0)
+ diff += mpctx->time_frame;
+ if (diff < 0)
+ diff = 0;
+ if (diff > 10)
+ diff = 10;
+ duration = diff * 1e6;
+ mpctx->last_frame_duration = diff;
+ }
+ if (mpctx->video_status != STATUS_PLAYING)
+ duration = -1;
+
+ MP_STATS(mpctx, "start flip");
+ vo_flip_page(vo, pts_us | 1, duration);
+ MP_STATS(mpctx, "end flip");
+
+ if (audio_pts != MP_NOPTS_VALUE)
+ MP_STATS(mpctx, "value %f ptsdiff", mpctx->video_pts - audio_pts);
+
+ mpctx->last_vo_flip_duration = (mp_time_us() - t2) * 0.000001;
+ if (vo->driver->flip_page_timed) {
+ // No need to adjust sync based on flip speed
+ mpctx->last_vo_flip_duration = 0;
+ // For print_status - VO call finishing early is OK for sync
+ mpctx->time_frame -= get_relative_time(mpctx);
+ }
+ mpctx->shown_vframes++;
+ if (mpctx->video_status < STATUS_PLAYING)
+ mpctx->video_status = STATUS_READY;
+ update_avsync(mpctx);
+ screenshot_flip(mpctx);
+
+ mp_notify(mpctx, MPV_EVENT_TICK, NULL);
+
+ if (!mpctx->sync_audio_to_video)
+ mpctx->video_status = STATUS_EOF;
+}