From 1423bd0bfd671ffc023778839a540fffd1dd3686 Mon Sep 17 00:00:00 2001 From: wm4 Date: Wed, 30 Jul 2014 23:24:08 +0200 Subject: 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. --- player/video.c | 324 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 322 insertions(+), 2 deletions(-) (limited to 'player/video.c') 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; +} -- cgit v1.2.3