void write_video(struct MPContext *mpctx, double endpts) { struct MPOpts *opts = mpctx->opts; struct vo *vo = mpctx->video_out; if (!mpctx->d_video) return; // 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; int r = video_output_image(mpctx, endpts); MP_TRACE(mpctx, "video_output_image: %d\n", r); if (r < 0) goto error; if (r == VD_WAIT) // Demuxer will wake us up for more packets to decode. return; if (r == VD_EOF) { mpctx->video_status = vo_still_displaying(vo) ? STATUS_DRAINING : STATUS_EOF; mpctx->delay = 0; mpctx->last_av_difference = 0; MP_DBG(mpctx, "video EOF (status=%d)\n", mpctx->video_status); return; } if (mpctx->video_status > STATUS_PLAYING) mpctx->video_status = STATUS_PLAYING; if (r != VD_NEW_FRAME) { mpctx->sleeptime = 0; // Decode more in next iteration. return; } // Filter output is different from VO input? struct mp_image_params p = mpctx->next_frames[0]->params; if (!vo->params || !mp_image_params_equal(&p, vo->params)) { // Changing config deletes the current frame; wait until it's finished. if (vo_still_displaying(vo)) return; const struct vo_driver *info = mpctx->video_out->driver; char extra[20] = {0}; if (p.w != p.d_w || p.h != p.d_h) snprintf(extra, sizeof(extra), " => %dx%d", p.d_w, p.d_h); MP_INFO(mpctx, "VO: [%s] %dx%d%s %s\n", info->name, p.w, p.h, extra, vo_format_name(p.imgfmt)); MP_VERBOSE(mpctx, "VO: Description: %s\n", info->description); int vo_r = vo_reconfig(vo, &p, 0); if (vo_r < 0) { mpctx->error_playing = MPV_ERROR_VO_INIT_FAILED; goto error; } init_vo(mpctx); } mpctx->time_frame -= get_relative_time(mpctx); update_avsync_before_frame(mpctx); double time_frame = MPMAX(mpctx->time_frame, -1); 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)) { if (video_feed_async_filter(mpctx) < 0) goto error; return; } assert(mpctx->num_next_frames >= 1); struct vo_frame dummy = { .pts = pts, .duration = -1, .num_frames = mpctx->num_next_frames, }; for (int n = 0; n < dummy.num_frames; n++) dummy.frames[n] = mpctx->next_frames[n]; struct vo_frame *frame = vo_frame_ref(&dummy); double diff = -1; double vpts0 = mpctx->next_frames[0]->pts; double vpts1 = MP_NOPTS_VALUE; if (mpctx->num_next_frames >= 2) vpts1 = mpctx->next_frames[1]->pts; if (vpts0 != MP_NOPTS_VALUE && vpts1 != MP_NOPTS_VALUE) diff = vpts1 - vpts0; 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 if (diff >= 0) { // expected A/V sync correction is ignored diff /= opts->playback_speed; if (mpctx->time_frame < 0) diff += mpctx->time_frame; frame->duration = MPCLAMP(diff, 0, 10) * 1e6; } mpctx->video_pts = mpctx->next_frames[0]->pts; mpctx->last_vo_pts = mpctx->video_pts; mpctx->playback_pts = mpctx->video_pts; update_avsync_after_frame(mpctx); mpctx->osd_force_update = true; update_osd_msg(mpctx); update_subtitles(mpctx); 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); mpctx->shown_vframes++; if (mpctx->video_status < STATUS_PLAYING) { mpctx->video_status = STATUS_READY; // After a seek, make sure to wait until the first frame is visible. vo_wait_frame(vo); MP_VERBOSE(mpctx, "first video frame after restart shown\n"); } screenshot_flip(mpctx); mp_notify(mpctx, MPV_EVENT_TICK, NULL); if (!mpctx->sync_audio_to_video) mpctx->video_status = STATUS_EOF; if (mpctx->video_status != STATUS_EOF) { if (mpctx->step_frames > 0) { mpctx->step_frames--; if (!mpctx->step_frames && !opts->pause) pause_player(mpctx); } if (mpctx->max_frames == 0 && !mpctx->stop_play) mpctx->stop_play = AT_END_OF_FILE; if (mpctx->max_frames > 0) mpctx->max_frames--; } mpctx->sleeptime = 0; return; error: MP_FATAL(mpctx, "Could not initialize video chain.\n"); uninit_video_chain(mpctx); error_on_track(mpctx, mpctx->current_track[STREAM_VIDEO][0]); handle_force_window(mpctx, true); mpctx->sleeptime = 0; }
int reinit_video_chain(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; assert(!mpctx->d_video); struct track *track = mpctx->current_track[0][STREAM_VIDEO]; struct sh_stream *sh = track ? track->stream : NULL; if (!sh) goto no_video; MP_VERBOSE(mpctx, "[V] fourcc:0x%X size:%dx%d fps:%5.3f\n", sh->format, sh->video->disp_w, sh->video->disp_h, sh->video->fps); //================== Init VIDEO (codec & libvo) ========================== if (!opts->fixed_vo || !mpctx->video_out) { struct vo_extra ex = { .input_ctx = mpctx->input, .osd = mpctx->osd, .encode_lavc_ctx = mpctx->encode_lavc_ctx, .opengl_cb_context = mpctx->gl_cb_ctx, }; mpctx->video_out = init_best_video_out(mpctx->global, &ex); if (!mpctx->video_out) { MP_FATAL(mpctx, "Error opening/initializing " "the selected video_out (-vo) device.\n"); mpctx->error_playing = MPV_ERROR_VO_INIT_FAILED; goto err_out; } mpctx->mouse_cursor_visible = true; } update_window_title(mpctx, true); struct dec_video *d_video = talloc_zero(NULL, struct dec_video); mpctx->d_video = d_video; d_video->global = mpctx->global; d_video->log = mp_log_new(d_video, mpctx->log, "!vd"); d_video->opts = mpctx->opts; d_video->header = sh; d_video->fps = sh->video->fps; d_video->vo = mpctx->video_out; if (opts->force_fps) { d_video->fps = opts->force_fps; MP_INFO(mpctx, "FPS forced to %5.3f.\n", d_video->fps); MP_INFO(mpctx, "Use --no-correct-pts to force FPS based timing.\n"); } update_fps(mpctx); vo_control(mpctx->video_out, VOCTRL_GET_HWDEC_INFO, &d_video->hwdec_info); recreate_video_filters(mpctx); if (!video_init_best_codec(d_video, opts->video_decoders)) goto err_out; bool saver_state = opts->pause || !opts->stop_screensaver; vo_control(mpctx->video_out, saver_state ? VOCTRL_RESTORE_SCREENSAVER : VOCTRL_KILL_SCREENSAVER, NULL); vo_set_paused(mpctx->video_out, mpctx->paused); mpctx->sync_audio_to_video = !sh->attached_picture; mpctx->vo_pts_history_seek_ts++; // If we switch on video again, ensure audio position matches up. if (mpctx->d_audio) mpctx->audio_status = STATUS_SYNCING; reset_video_state(mpctx); reset_subtitle_state(mpctx); return 1; err_out: no_video: uninit_video_chain(mpctx); if (track) error_on_track(mpctx, track); handle_force_window(mpctx, true); return 0; } // Try to refresh the video by doing a precise seek to the currently displayed // frame. This can go wrong in all sorts of ways, so use sparingly. void mp_force_video_refresh(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; struct dec_video *d_video = mpctx->d_video; if (!d_video || !d_video->decoder_output.imgfmt) return; // If not paused, the next frame should come soon enough. if (opts->pause && mpctx->last_vo_pts != MP_NOPTS_VALUE) queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->last_vo_pts, 2, true); } static int check_framedrop(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; // check for frame-drop: if (mpctx->video_status == STATUS_PLAYING && !mpctx->paused && mpctx->audio_status == STATUS_PLAYING && !ao_untimed(mpctx->ao)) { float fps = mpctx->d_video->fps; double frame_time = fps > 0 ? 1.0 / fps : 0; // we should avoid dropping too many frames in sequence unless we // are too late. and we allow 100ms A-V delay here: if (mpctx->last_av_difference - 0.100 > mpctx->dropped_frames * frame_time) return !!(opts->frame_dropping & 2); } return 0; } // Read a packet, store decoded image into d_video->waiting_decoded_mpi // returns VD_* code static int decode_image(struct MPContext *mpctx) { struct dec_video *d_video = mpctx->d_video; if (d_video->header->attached_picture) { d_video->waiting_decoded_mpi = video_decode(d_video, d_video->header->attached_picture, 0); return VD_EOF; } struct demux_packet *pkt; if (demux_read_packet_async(d_video->header, &pkt) == 0) return VD_WAIT; if (pkt && pkt->pts != MP_NOPTS_VALUE) pkt->pts += mpctx->video_offset; if ((pkt && pkt->pts >= mpctx->hrseek_pts - .005) || d_video->has_broken_packet_pts || !mpctx->opts->hr_seek_framedrop) { mpctx->hrseek_framedrop = false; } bool hrseek = mpctx->hrseek_active && mpctx->video_status == STATUS_SYNCING; int framedrop_type = hrseek && mpctx->hrseek_framedrop ? 2 : check_framedrop(mpctx); d_video->waiting_decoded_mpi = video_decode(d_video, pkt, framedrop_type); bool had_packet = !!pkt; talloc_free(pkt); if (had_packet && !d_video->waiting_decoded_mpi && mpctx->video_status == STATUS_PLAYING) { mpctx->dropped_frames_total++; mpctx->dropped_frames++; } return had_packet ? VD_PROGRESS : VD_EOF; } // Called after video reinit. This can be generally used to try to insert more // filters using the filter chain edit functionality in command.c. static void init_filter_params(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; // Note that the filter chain is already initialized. This code might // recreate the chain a second time, which is not very elegant, but allows // us to test whether enabling deinterlacing works with the current video // format and other filters. if (opts->deinterlace >= 0) mp_property_do("deinterlace", M_PROPERTY_SET, &opts->deinterlace, mpctx); } // Feed newly decoded frames to the filter, take care of format changes. // If eof=true, drain the filter chain, and return VD_EOF if empty. static int video_filter(struct MPContext *mpctx, bool eof) { struct dec_video *d_video = mpctx->d_video; struct vf_chain *vf = d_video->vfilter; if (vf->initialized < 0) return VD_ERROR; // There is already a filtered frame available. // If vf_needs_input() returns > 0, the filter wants input anyway. if (vf_output_frame(vf, eof) > 0 && vf_needs_input(vf) < 1) return VD_PROGRESS; // Decoder output is different from filter input? bool need_vf_reconfig = !vf->input_params.imgfmt || vf->initialized < 1 || !mp_image_params_equal(&d_video->decoder_output, &vf->input_params); // (If imgfmt==0, nothing was decoded yet, and the format is unknown.) if (need_vf_reconfig && d_video->decoder_output.imgfmt) { // Drain the filter chain. if (vf_output_frame(vf, true) > 0) return VD_PROGRESS; // The filter chain is drained; execute the filter format change. filter_reconfig(mpctx, false); if (vf->initialized == 0) return VD_PROGRESS; // hw decoding fallback; try again if (vf->initialized < 1) return VD_ERROR; init_filter_params(mpctx); return VD_RECONFIG; } // If something was decoded, and the filter chain is ready, filter it. if (!need_vf_reconfig && d_video->waiting_decoded_mpi) { vf_filter_frame(vf, d_video->waiting_decoded_mpi); d_video->waiting_decoded_mpi = NULL; return VD_PROGRESS; } return eof ? VD_EOF : VD_PROGRESS; } // Make sure at least 1 filtered image is available, decode new video if needed. // returns VD_* code // A return value of VD_PROGRESS doesn't necessarily output a frame, but makes // the promise that calling this function again will eventually do something. static int video_decode_and_filter(struct MPContext *mpctx) { struct dec_video *d_video = mpctx->d_video; int r = video_filter(mpctx, false); if (r < 0) return r; if (!d_video->waiting_decoded_mpi) { // Decode a new image, or at least feed the decoder a packet. r = decode_image(mpctx); if (r == VD_WAIT) return r; if (d_video->waiting_decoded_mpi) d_video->decoder_output = d_video->waiting_decoded_mpi->params; } bool eof = !d_video->waiting_decoded_mpi && (r == VD_EOF || r < 0); r = video_filter(mpctx, eof); if (r == VD_RECONFIG) // retry feeding decoded image r = video_filter(mpctx, eof); return r; } static int video_feed_async_filter(struct MPContext *mpctx) { struct dec_video *d_video = mpctx->d_video; struct vf_chain *vf = d_video->vfilter; if (vf->initialized < 0) return VD_ERROR; if (vf_needs_input(vf) < 1) return 0; mpctx->sleeptime = 0; // retry until done return video_decode_and_filter(mpctx); } /* 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 v_pts, double frame_time) { struct MPOpts *opts = mpctx->opts; if (mpctx->audio_status != STATUS_PLAYING) return; double a_pts = written_audio_pts(mpctx) + mpctx->audio_delay - mpctx->delay; double av_delay = a_pts - v_pts; 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; } // Enough video filtered already to push one frame to the VO? static bool have_new_frame(struct MPContext *mpctx) { bool need_2nd = !!(mpctx->opts->frame_dropping & 1) // we need the duration && mpctx->video_pts != MP_NOPTS_VALUE; // ...except for the 1st frame return mpctx->next_frame[0] && (!need_2nd || mpctx->next_frame[1]); }
int reinit_video_chain(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; assert(!mpctx->d_video); struct track *track = mpctx->current_track[0][STREAM_VIDEO]; struct sh_stream *sh = track ? track->stream : NULL; if (!sh) goto no_video; if (!mpctx->video_out) { struct vo_extra ex = { .input_ctx = mpctx->input, .osd = mpctx->osd, .encode_lavc_ctx = mpctx->encode_lavc_ctx, .opengl_cb_context = mpctx->gl_cb_ctx, }; mpctx->video_out = init_best_video_out(mpctx->global, &ex); if (!mpctx->video_out) { MP_FATAL(mpctx, "Error opening/initializing " "the selected video_out (-vo) device.\n"); mpctx->error_playing = MPV_ERROR_VO_INIT_FAILED; goto err_out; } mpctx->mouse_cursor_visible = true; } update_window_title(mpctx, true); struct dec_video *d_video = talloc_zero(NULL, struct dec_video); mpctx->d_video = d_video; d_video->global = mpctx->global; d_video->log = mp_log_new(d_video, mpctx->log, "!vd"); d_video->opts = mpctx->opts; d_video->header = sh; d_video->fps = sh->video->fps; d_video->vo = mpctx->video_out; MP_VERBOSE(d_video, "Container reported FPS: %f\n", sh->video->fps); if (opts->force_fps) { d_video->fps = opts->force_fps; MP_INFO(mpctx, "FPS forced to %5.3f.\n", d_video->fps); MP_INFO(mpctx, "Use --no-correct-pts to force FPS based timing.\n"); } #if HAVE_ENCODING if (mpctx->encode_lavc_ctx && d_video) encode_lavc_set_video_fps(mpctx->encode_lavc_ctx, d_video->fps); #endif vo_control(mpctx->video_out, VOCTRL_GET_HWDEC_INFO, &d_video->hwdec_info); recreate_video_filters(mpctx); if (!video_init_best_codec(d_video, opts->video_decoders)) goto err_out; if (d_video->header->attached_picture && !decode_coverart(d_video)) goto err_out; bool saver_state = opts->pause || !opts->stop_screensaver; vo_control(mpctx->video_out, saver_state ? VOCTRL_RESTORE_SCREENSAVER : VOCTRL_KILL_SCREENSAVER, NULL); vo_set_paused(mpctx->video_out, mpctx->paused); mpctx->sync_audio_to_video = !sh->attached_picture; mpctx->vo_pts_history_seek_ts++; // If we switch on video again, ensure audio position matches up. if (mpctx->d_audio) mpctx->audio_status = STATUS_SYNCING; reset_video_state(mpctx); reset_subtitle_state(mpctx); return 1; err_out: no_video: uninit_video_chain(mpctx); if (track) error_on_track(mpctx, track); handle_force_window(mpctx, true); return 0; } // Try to refresh the video by doing a precise seek to the currently displayed // frame. This can go wrong in all sorts of ways, so use sparingly. void mp_force_video_refresh(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; struct dec_video *d_video = mpctx->d_video; if (!d_video || !d_video->decoder_output.imgfmt) return; // If not paused, the next frame should come soon enough. if (opts->pause && mpctx->video_status == STATUS_PLAYING && mpctx->last_vo_pts != MP_NOPTS_VALUE) { queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->last_vo_pts, MPSEEK_VERY_EXACT, true); } } static int check_framedrop(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; // check for frame-drop: if (mpctx->video_status == STATUS_PLAYING && !mpctx->paused && mpctx->audio_status == STATUS_PLAYING && !ao_untimed(mpctx->ao)) { float fps = mpctx->d_video->fps; double frame_time = fps > 0 ? 1.0 / fps : 0; // we should avoid dropping too many frames in sequence unless we // are too late. and we allow 100ms A-V delay here: if (mpctx->last_av_difference - 0.100 > mpctx->dropped_frames * frame_time) return !!(opts->frame_dropping & 2); } return 0; } static bool decode_coverart(struct dec_video *d_video) { d_video->cover_art_mpi = video_decode(d_video, d_video->header->attached_picture, 0); // Might need flush. if (!d_video->cover_art_mpi) d_video->cover_art_mpi = video_decode(d_video, NULL, 0); return !!d_video->cover_art_mpi; }
int reinit_video_chain(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; assert(!(mpctx->initialized_flags & INITIALIZED_VCODEC)); init_demux_stream(mpctx, STREAM_VIDEO); sh_video_t *sh_video = mpctx->sh_video; if (!sh_video) goto no_video; MP_VERBOSE(mpctx, "[V] fourcc:0x%X size:%dx%d fps:%5.3f\n", mpctx->sh_video->format, mpctx->sh_video->disp_w, mpctx->sh_video->disp_h, mpctx->sh_video->fps); if (opts->force_fps) mpctx->sh_video->fps = opts->force_fps; update_fps(mpctx); if (!mpctx->sh_video->fps && !opts->force_fps && !opts->correct_pts) { MP_ERR(mpctx, "FPS not specified in the " "header or invalid, use the -fps option.\n"); } double ar = -1.0; //================== Init VIDEO (codec & libvo) ========================== if (!opts->fixed_vo || !(mpctx->initialized_flags & INITIALIZED_VO)) { mpctx->video_out = init_best_video_out(mpctx->global, mpctx->input, mpctx->encode_lavc_ctx); if (!mpctx->video_out) { MP_FATAL(mpctx, "Error opening/initializing " "the selected video_out (-vo) device.\n"); goto err_out; } mpctx->mouse_cursor_visible = true; mpctx->initialized_flags |= INITIALIZED_VO; } // dynamic allocation only to make stheader.h lighter talloc_free(sh_video->hwdec_info); sh_video->hwdec_info = talloc_zero(sh_video, struct mp_hwdec_info); vo_control(mpctx->video_out, VOCTRL_GET_HWDEC_INFO, sh_video->hwdec_info); update_window_title(mpctx, true); if (stream_control(mpctx->sh_video->gsh->demuxer->stream, STREAM_CTRL_GET_ASPECT_RATIO, &ar) != STREAM_UNSUPPORTED) mpctx->sh_video->stream_aspect = ar; recreate_video_filters(mpctx); init_best_video_codec(sh_video, opts->video_decoders); if (!sh_video->initialized) goto err_out; mpctx->initialized_flags |= INITIALIZED_VCODEC; bool saver_state = opts->pause || !opts->stop_screensaver; vo_control(mpctx->video_out, saver_state ? VOCTRL_RESTORE_SCREENSAVER : VOCTRL_KILL_SCREENSAVER, NULL); vo_control(mpctx->video_out, mpctx->paused ? VOCTRL_PAUSE : VOCTRL_RESUME, NULL); sh_video->last_pts = MP_NOPTS_VALUE; sh_video->num_buffered_pts = 0; sh_video->next_frame_time = 0; mpctx->last_vf_reconfig_count = 0; mpctx->restart_playback = true; mpctx->sync_audio_to_video = !sh_video->gsh->attached_picture; mpctx->delay = 0; mpctx->vo_pts_history_seek_ts++; vo_seek_reset(mpctx->video_out); reset_subtitles(mpctx); return 1; err_out: no_video: uninit_player(mpctx, INITIALIZED_VCODEC | (opts->force_vo ? 0 : INITIALIZED_VO)); cleanup_demux_stream(mpctx, STREAM_VIDEO); mpctx->current_track[STREAM_VIDEO] = NULL; handle_force_window(mpctx, true); MP_INFO(mpctx, "Video: no video\n"); return 0; }
int reinit_video_chain(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; assert(!(mpctx->initialized_flags & INITIALIZED_VCODEC)); assert(!mpctx->d_video); struct track *track = mpctx->current_track[0][STREAM_VIDEO]; struct sh_stream *sh = init_demux_stream(mpctx, track); if (!sh) goto no_video; MP_VERBOSE(mpctx, "[V] fourcc:0x%X size:%dx%d fps:%5.3f\n", sh->format, sh->video->disp_w, sh->video->disp_h, sh->video->fps); double ar = -1.0; //================== Init VIDEO (codec & libvo) ========================== if (!opts->fixed_vo || !(mpctx->initialized_flags & INITIALIZED_VO)) { mpctx->video_out = init_best_video_out(mpctx->global, mpctx->input, mpctx->encode_lavc_ctx); if (!mpctx->video_out) { MP_FATAL(mpctx, "Error opening/initializing " "the selected video_out (-vo) device.\n"); goto err_out; } mpctx->mouse_cursor_visible = true; mpctx->initialized_flags |= INITIALIZED_VO; } update_window_title(mpctx, true); struct dec_video *d_video = talloc_zero(NULL, struct dec_video); mpctx->d_video = d_video; d_video->global = mpctx->global; d_video->log = mp_log_new(d_video, mpctx->log, "!vd"); d_video->opts = mpctx->opts; d_video->header = sh; d_video->fps = sh->video->fps; d_video->vo = mpctx->video_out; mpctx->initialized_flags |= INITIALIZED_VCODEC; vo_control(mpctx->video_out, VOCTRL_GET_HWDEC_INFO, &d_video->hwdec_info); if (stream_control(sh->demuxer->stream, STREAM_CTRL_GET_ASPECT_RATIO, &ar) != STREAM_UNSUPPORTED) d_video->stream_aspect = ar; recreate_video_filters(mpctx); if (!video_init_best_codec(d_video, opts->video_decoders)) goto err_out; bool saver_state = opts->pause || !opts->stop_screensaver; vo_control(mpctx->video_out, saver_state ? VOCTRL_RESTORE_SCREENSAVER : VOCTRL_KILL_SCREENSAVER, NULL); vo_control(mpctx->video_out, mpctx->paused ? VOCTRL_PAUSE : VOCTRL_RESUME, NULL); mpctx->restart_playback = true; mpctx->sync_audio_to_video = !sh->attached_picture; mpctx->delay = 0; mpctx->video_next_pts = MP_NOPTS_VALUE; mpctx->playing_last_frame = false; mpctx->last_frame_duration = 0; mpctx->vo_pts_history_seek_ts++; vo_seek_reset(mpctx->video_out); reset_subtitles(mpctx, 0); reset_subtitles(mpctx, 1); if (opts->force_fps) { d_video->fps = opts->force_fps; MP_INFO(mpctx, "FPS forced to be %5.3f.\n", d_video->fps); } if (!sh->video->fps && !opts->force_fps && !opts->correct_pts) { MP_ERR(mpctx, "FPS not specified in the " "header or invalid, use the -fps option.\n"); } update_fps(mpctx); return 1; err_out: no_video: uninit_player(mpctx, INITIALIZED_VCODEC | (opts->force_vo ? 0 : INITIALIZED_VO)); mp_deselect_track(mpctx, track); handle_force_window(mpctx, true); MP_INFO(mpctx, "Video: no video\n"); return 0; }
int reinit_video_chain(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; assert(!mpctx->d_video); struct track *track = mpctx->current_track[0][STREAM_VIDEO]; struct sh_stream *sh = track ? track->stream : NULL; if (!sh) goto no_video; MP_VERBOSE(mpctx, "[V] fourcc:0x%X size:%dx%d fps:%5.3f\n", sh->format, sh->video->disp_w, sh->video->disp_h, sh->video->fps); //================== Init VIDEO (codec & libvo) ========================== if (!opts->fixed_vo || !mpctx->video_out) { mpctx->video_out = init_best_video_out(mpctx->global, mpctx->input, mpctx->osd, mpctx->encode_lavc_ctx); if (!mpctx->video_out) { MP_FATAL(mpctx, "Error opening/initializing " "the selected video_out (-vo) device.\n"); mpctx->error_playing = MPV_ERROR_VO_INIT_FAILED; goto err_out; } mpctx->mouse_cursor_visible = true; } update_window_title(mpctx, true); struct dec_video *d_video = talloc_zero(NULL, struct dec_video); mpctx->d_video = d_video; d_video->global = mpctx->global; d_video->log = mp_log_new(d_video, mpctx->log, "!vd"); d_video->opts = mpctx->opts; d_video->header = sh; d_video->fps = sh->video->fps; d_video->vo = mpctx->video_out; vo_control(mpctx->video_out, VOCTRL_GET_HWDEC_INFO, &d_video->hwdec_info); recreate_video_filters(mpctx); if (!video_init_best_codec(d_video, opts->video_decoders)) goto err_out; bool saver_state = opts->pause || !opts->stop_screensaver; vo_control(mpctx->video_out, saver_state ? VOCTRL_RESTORE_SCREENSAVER : VOCTRL_KILL_SCREENSAVER, NULL); vo_set_paused(mpctx->video_out, mpctx->paused); mpctx->sync_audio_to_video = !sh->attached_picture; mpctx->vo_pts_history_seek_ts++; // If we switch on video again, ensure audio position matches up. if (mpctx->d_audio) mpctx->audio_status = STATUS_SYNCING; reset_video_state(mpctx); reset_subtitle_state(mpctx); if (opts->force_fps) { d_video->fps = opts->force_fps; MP_INFO(mpctx, "FPS forced to be %5.3f.\n", d_video->fps); } if (!sh->video->fps && !opts->force_fps && !opts->correct_pts) { MP_ERR(mpctx, "FPS not specified in the " "header or invalid, use the -fps option.\n"); } update_fps(mpctx); return 1; err_out: no_video: uninit_video_chain(mpctx); if (track) error_on_track(mpctx, track); handle_force_window(mpctx, true); return 0; }
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"); return; } 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; }
void write_video(struct MPContext *mpctx, double endpts) { struct MPOpts *opts = mpctx->opts; struct vo *vo = mpctx->video_out; if (!mpctx->d_video) return; // 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; update_fps(mpctx); int r = video_output_image(mpctx, endpts); MP_TRACE(mpctx, "video_output_image: %d\n", r); if (r < 0) goto error; if (r == VD_WAIT) // Demuxer will wake us up for more packets to decode. return; if (r == VD_EOF) { mpctx->video_status = vo_still_displaying(vo) ? STATUS_DRAINING : STATUS_EOF; mpctx->delay = 0; mpctx->last_av_difference = 0; MP_VERBOSE(mpctx, "video EOF (status=%d)\n", mpctx->video_status); return; } if (mpctx->video_status > STATUS_PLAYING) mpctx->video_status = STATUS_PLAYING; mpctx->time_frame -= get_relative_time(mpctx); update_avsync_before_frame(mpctx); if (r != VD_NEW_FRAME) { mpctx->sleeptime = 0; // Decode more in next iteration. return; } // Filter output is different from VO input? struct mp_image_params p = mpctx->next_frame[0]->params; if (!vo->params || !mp_image_params_equal(&p, vo->params)) { // Changing config deletes the current frame; wait until it's finished. if (vo_still_displaying(vo)) return; const struct vo_driver *info = mpctx->video_out->driver; MP_INFO(mpctx, "VO: [%s] %dx%d => %dx%d %s\n", info->name, p.w, p.h, p.d_w, p.d_h, vo_format_name(p.imgfmt)); MP_VERBOSE(mpctx, "VO: Description: %s\n", info->description); int vo_r = vo_reconfig(vo, &p, 0); if (vo_r < 0) goto error; init_vo(mpctx); mpctx->time_frame = 0; // display immediately } double time_frame = MPMAX(mpctx->time_frame, -1); int64_t pts = mp_time_us() + (int64_t)(time_frame * 1e6); if (!vo_is_ready_for_frame(vo, pts)) return; // wait until VO wakes us up to get more frames int64_t duration = -1; double diff = -1; double vpts0 = mpctx->next_frame[0] ? mpctx->next_frame[0]->pts : MP_NOPTS_VALUE; double vpts1 = mpctx->next_frame[1] ? mpctx->next_frame[1]->pts : MP_NOPTS_VALUE; if (vpts0 != MP_NOPTS_VALUE && vpts1 != MP_NOPTS_VALUE) diff = vpts1 - vpts0; if (diff < 0 && mpctx->d_video->fps > 0) diff = 1.0 / mpctx->d_video->fps; // fallback to demuxer-reported fps if (diff >= 0) { // expected A/V sync correction is ignored diff /= opts->playback_speed; if (mpctx->time_frame < 0) diff += mpctx->time_frame; duration = MPCLAMP(diff, 0, 10) * 1e6; } mpctx->video_pts = mpctx->next_frame[0]->pts; mpctx->last_vo_pts = mpctx->video_pts; mpctx->playback_pts = mpctx->video_pts; mpctx->osd_force_update = true; update_osd_msg(mpctx); update_subtitles(mpctx); vo_queue_frame(vo, mpctx->next_frame[0], pts, duration); mpctx->next_frame[0] = NULL; mpctx->shown_vframes++; if (mpctx->video_status < STATUS_PLAYING) { mpctx->video_status = STATUS_READY; // After a seek, make sure to wait until the first frame is visible. vo_wait_frame(vo); } update_avsync_after_frame(mpctx); screenshot_flip(mpctx); mp_notify(mpctx, MPV_EVENT_TICK, NULL); if (!mpctx->sync_audio_to_video) mpctx->video_status = STATUS_EOF; if (mpctx->video_status != STATUS_EOF) { if (mpctx->step_frames > 0) { mpctx->step_frames--; if (!mpctx->step_frames && !opts->pause) pause_player(mpctx); } if (mpctx->max_frames == 0) mpctx->stop_play = PT_NEXT_ENTRY; if (mpctx->max_frames > 0) mpctx->max_frames--; } mpctx->sleeptime = 0; return; error: 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); mpctx->sleeptime = 0; }