summaryrefslogtreecommitdiffstats
path: root/mpvcore/player/video.c
diff options
context:
space:
mode:
Diffstat (limited to 'mpvcore/player/video.c')
-rw-r--r--mpvcore/player/video.c474
1 files changed, 474 insertions, 0 deletions
diff --git a/mpvcore/player/video.c b/mpvcore/player/video.c
new file mode 100644
index 0000000000..a6e9f8aecb
--- /dev/null
+++ b/mpvcore/player/video.c
@@ -0,0 +1,474 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <math.h>
+#include <assert.h>
+
+#include "config.h"
+#include "talloc.h"
+
+#include "mpvcore/mp_msg.h"
+#include "mpvcore/options.h"
+#include "mpvcore/mp_common.h"
+#include "mpvcore/encode.h"
+#include "mpvcore/m_property.h"
+
+#include "audio/out/ao.h"
+#include "demux/demux.h"
+#include "stream/stream.h"
+#include "sub/sub.h"
+#include "video/filter/vf.h"
+#include "video/decode/dec_video.h"
+#include "video/out/vo.h"
+
+#include "mp_core.h"
+#include "command.h"
+
+void update_fps(struct MPContext *mpctx)
+{
+#ifdef CONFIG_ENCODING
+ struct sh_video *sh_video = mpctx->sh_video;
+ if (mpctx->encode_lavc_ctx && sh_video)
+ encode_lavc_set_video_fps(mpctx->encode_lavc_ctx, sh_video->fps);
+#endif
+}
+
+static void recreate_video_filters(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct sh_video *sh_video = mpctx->sh_video;
+ assert(sh_video);
+
+ vf_uninit_filter_chain(sh_video->vfilter);
+
+ char *vf_arg[] = {
+ "_oldargs_", (char *)mpctx->video_out, NULL
+ };
+ sh_video->vfilter = vf_open_filter(opts, NULL, "vo", vf_arg);
+
+ sh_video->vfilter = append_filters(sh_video->vfilter, opts->vf_settings);
+
+ struct vf_instance *vf = sh_video->vfilter;
+ mpctx->osd->render_subs_in_filter
+ = vf->control(vf, VFCTRL_INIT_OSD, NULL) == VO_TRUE;
+}
+
+int reinit_video_filters(struct MPContext *mpctx)
+{
+ struct sh_video *sh_video = mpctx->sh_video;
+
+ if (!sh_video)
+ return -2;
+
+ recreate_video_filters(mpctx);
+ video_reinit_vo(sh_video);
+
+ return sh_video->vf_initialized > 0 ? 0 : -1;
+}
+
+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_vo_window_title(mpctx);
+
+ 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);
+ handle_force_window(mpctx, true);
+ MP_INFO(mpctx, "Video: no video\n");
+ 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;
+
+ // 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, 1);
+}
+
+static bool filter_output_queued_frame(struct MPContext *mpctx)
+{
+ struct sh_video *sh_video = mpctx->sh_video;
+ struct vo *video_out = mpctx->video_out;
+
+ struct mp_image *img = vf_chain_output_queued_frame(sh_video->vfilter);
+ if (img)
+ vo_queue_image(video_out, img);
+ talloc_free(img);
+
+ return !!img;
+}
+
+static bool load_next_vo_frame(struct MPContext *mpctx, bool eof)
+{
+ if (vo_get_buffered_frame(mpctx->video_out, eof) >= 0)
+ return true;
+ if (filter_output_queued_frame(mpctx))
+ return true;
+ return false;
+}
+
+static void init_filter_params(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct sh_video *sh_video = mpctx->sh_video;
+
+ // Note that the video decoder already initializes the filter chain. This
+ // 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 (sh_video->vf_initialized != 1)
+ return;
+
+ if (sh_video->vf_reconfig_count <= mpctx->last_vf_reconfig_count) {
+ if (opts->deinterlace >= 0) {
+ mp_property_do("deinterlace", M_PROPERTY_SET, &opts->deinterlace,
+ mpctx);
+ }
+ }
+ // Setting filter params has to be "stable" (no change if params already
+ // set) - checking the reconfig count is just an optimization.
+ mpctx->last_vf_reconfig_count = sh_video->vf_reconfig_count;
+}
+
+static void filter_video(struct MPContext *mpctx, struct mp_image *frame)
+{
+ struct sh_video *sh_video = mpctx->sh_video;
+
+ init_filter_params(mpctx);
+
+ frame->pts = sh_video->pts;
+ mp_image_set_params(frame, sh_video->vf_input);
+ vf_filter_frame(sh_video->vfilter, frame);
+ filter_output_queued_frame(mpctx);
+}
+
+static int check_framedrop(struct MPContext *mpctx, double frame_time)
+{
+ struct MPOpts *opts = mpctx->opts;
+ // check for frame-drop:
+ if (mpctx->sh_audio && !mpctx->ao->untimed &&
+ !demux_stream_eof(mpctx->sh_audio->gsh))
+ {
+ float delay = opts->playback_speed * ao_get_delay(mpctx->ao);
+ float d = delay - mpctx->delay;
+ if (frame_time < 0)
+ frame_time = mpctx->sh_video->fps > 0 ? 1.0 / mpctx->sh_video->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 (d < -mpctx->dropped_frames * frame_time - 0.100 && !mpctx->paused
+ && !mpctx->restart_playback) {
+ mpctx->drop_frame_cnt++;
+ mpctx->dropped_frames++;
+ return mpctx->opts->frame_dropping;
+ } else
+ mpctx->dropped_frames = 0;
+ }
+ return 0;
+}
+
+static struct demux_packet *video_read_frame(struct MPContext *mpctx)
+{
+ sh_video_t *sh_video = mpctx->sh_video;
+ demuxer_t *demuxer = sh_video->gsh->demuxer;
+ float pts1 = sh_video->last_pts;
+
+ struct demux_packet *pkt = demux_read_packet(sh_video->gsh);
+ if (!pkt)
+ return NULL; // EOF
+
+ if (pkt->pts != MP_NOPTS_VALUE)
+ sh_video->last_pts = pkt->pts;
+
+ float frame_time = sh_video->fps > 0 ? 1.0f / sh_video->fps : 0;
+
+ // override frame_time for variable/unknown FPS formats:
+ if (!mpctx->opts->force_fps) {
+ double next_pts = demux_get_next_pts(sh_video->gsh);
+ double d = next_pts == MP_NOPTS_VALUE ? sh_video->last_pts - pts1
+ : next_pts - sh_video->last_pts;
+ if (d >= 0) {
+ if (demuxer->type == DEMUXER_TYPE_TV) {
+ if (d > 0)
+ sh_video->fps = 1.0f / d;
+ frame_time = d;
+ } else {
+ if ((int)sh_video->fps <= 1)
+ frame_time = d;
+ }
+ }
+ }
+
+ sh_video->pts = sh_video->last_pts;
+ sh_video->next_frame_time = frame_time;
+ return pkt;
+}
+
+static double update_video_nocorrect_pts(struct MPContext *mpctx)
+{
+ struct sh_video *sh_video = mpctx->sh_video;
+ double frame_time = 0;
+ while (1) {
+ // In nocorrect-pts mode there is no way to properly time these frames
+ if (load_next_vo_frame(mpctx, false))
+ break;
+ frame_time = sh_video->next_frame_time;
+ if (mpctx->restart_playback)
+ frame_time = 0;
+ struct demux_packet *pkt = video_read_frame(mpctx);
+ if (!pkt)
+ return -1;
+ if (mpctx->sh_audio)
+ mpctx->delay -= frame_time;
+ // video_read_frame can change fps (e.g. for ASF video)
+ update_fps(mpctx);
+ int framedrop_type = check_framedrop(mpctx, frame_time);
+
+ void *decoded_frame = decode_video(sh_video, pkt, framedrop_type,
+ sh_video->pts);
+ talloc_free(pkt);
+ if (decoded_frame) {
+ filter_video(mpctx, decoded_frame);
+ }
+ break;
+ }
+ return frame_time;
+}
+
+static double update_video_attached_pic(struct MPContext *mpctx)
+{
+ struct sh_video *sh_video = mpctx->sh_video;
+
+ // Try to decode the picture multiple times, until it is displayed.
+ if (mpctx->video_out->hasframe)
+ return -1;
+
+ struct mp_image *decoded_frame =
+ decode_video(sh_video, sh_video->gsh->attached_picture, 0, 0);
+ if (decoded_frame)
+ filter_video(mpctx, decoded_frame);
+ load_next_vo_frame(mpctx, true);
+ mpctx->sh_video->pts = MP_NOPTS_VALUE;
+ return 0;
+}
+
+static void determine_frame_pts(struct MPContext *mpctx)
+{
+ struct sh_video *sh_video = mpctx->sh_video;
+ struct MPOpts *opts = mpctx->opts;
+
+ if (opts->user_pts_assoc_mode)
+ sh_video->pts_assoc_mode = opts->user_pts_assoc_mode;
+ else if (sh_video->pts_assoc_mode == 0) {
+ if (mpctx->sh_video->gsh->demuxer->timestamp_type == TIMESTAMP_TYPE_PTS
+ && sh_video->codec_reordered_pts != MP_NOPTS_VALUE)
+ sh_video->pts_assoc_mode = 1;
+ else
+ sh_video->pts_assoc_mode = 2;
+ } else {
+ int probcount1 = sh_video->num_reordered_pts_problems;
+ int probcount2 = sh_video->num_sorted_pts_problems;
+ if (sh_video->pts_assoc_mode == 2) {
+ int tmp = probcount1;
+ probcount1 = probcount2;
+ probcount2 = tmp;
+ }
+ if (probcount1 >= probcount2 * 1.5 + 2) {
+ sh_video->pts_assoc_mode = 3 - sh_video->pts_assoc_mode;
+ MP_VERBOSE(mpctx, "Switching to pts association mode "
+ "%d.\n", sh_video->pts_assoc_mode);
+ }
+ }
+ sh_video->pts = sh_video->pts_assoc_mode == 1 ?
+ sh_video->codec_reordered_pts : sh_video->sorted_pts;
+}
+
+double update_video(struct MPContext *mpctx, double endpts)
+{
+ struct sh_video *sh_video = mpctx->sh_video;
+ struct vo *video_out = mpctx->video_out;
+ sh_video->vfilter->control(sh_video->vfilter, VFCTRL_SET_OSD_OBJ,
+ mpctx->osd); // for vf_sub
+ if (!mpctx->opts->correct_pts)
+ return update_video_nocorrect_pts(mpctx);
+
+ if (sh_video->gsh->attached_picture)
+ return update_video_attached_pic(mpctx);
+
+ double pts;
+
+ while (1) {
+ if (load_next_vo_frame(mpctx, false))
+ break;
+ pts = MP_NOPTS_VALUE;
+ struct demux_packet *pkt = NULL;
+ while (1) {
+ pkt = demux_read_packet(mpctx->sh_video->gsh);
+ if (!pkt || pkt->len)
+ break;
+ /* Packets with size 0 are assumed to not correspond to frames,
+ * but to indicate the absence of a frame in formats like AVI
+ * that must have packets at fixed timecode intervals. */
+ talloc_free(pkt);
+ }
+ if (pkt)
+ pts = pkt->pts;
+ if (pts != MP_NOPTS_VALUE)
+ pts += mpctx->video_offset;
+ if (pts >= mpctx->hrseek_pts - .005)
+ mpctx->hrseek_framedrop = false;
+ int framedrop_type = mpctx->hrseek_active && mpctx->hrseek_framedrop ?
+ 1 : check_framedrop(mpctx, -1);
+ struct mp_image *decoded_frame =
+ decode_video(sh_video, pkt, framedrop_type, pts);
+ talloc_free(pkt);
+ if (decoded_frame) {
+ determine_frame_pts(mpctx);
+ filter_video(mpctx, decoded_frame);
+ } else if (!pkt) {
+ if (!load_next_vo_frame(mpctx, true))
+ return -1;
+ }
+ break;
+ }
+
+ if (!video_out->frame_loaded)
+ return 0;
+
+ pts = video_out->next_pts;
+ if (pts == MP_NOPTS_VALUE) {
+ MP_ERR(mpctx, "Video pts after filters MISSING\n");
+ // Try to use decoder pts from before filters
+ pts = sh_video->pts;
+ if (pts == MP_NOPTS_VALUE)
+ pts = sh_video->last_pts;
+ }
+ if (endpts == MP_NOPTS_VALUE || pts < endpts)
+ add_frame_pts(mpctx, pts);
+ if (mpctx->hrseek_active && pts < mpctx->hrseek_pts - .005) {
+ vo_skip_frame(video_out);
+ return 0;
+ }
+ mpctx->hrseek_active = false;
+ sh_video->pts = pts;
+ if (sh_video->last_pts == MP_NOPTS_VALUE)
+ sh_video->last_pts = sh_video->pts;
+ else if (sh_video->last_pts > sh_video->pts) {
+ MP_WARN(mpctx, "Decreasing video pts: %f < %f\n",
+ sh_video->pts, sh_video->last_pts);
+ /* If the difference in pts is small treat it as jitter around the
+ * right value (possibly caused by incorrect timestamp ordering) and
+ * just show this frame immediately after the last one.
+ * Treat bigger differences as timestamp resets and start counting
+ * timing of later frames from the position of this one. */
+ if (sh_video->last_pts - sh_video->pts > 0.5)
+ sh_video->last_pts = sh_video->pts;
+ else
+ sh_video->pts = sh_video->last_pts;
+ } else if (sh_video->pts >= sh_video->last_pts + 60) {
+ // Assume a PTS difference >= 60 seconds is a discontinuity.
+ MP_WARN(mpctx, "Jump in video pts: %f -> %f\n",
+ sh_video->last_pts, sh_video->pts);
+ sh_video->last_pts = sh_video->pts;
+ }
+ double frame_time = sh_video->pts - sh_video->last_pts;
+ sh_video->last_pts = sh_video->pts;
+ if (mpctx->sh_audio)
+ mpctx->delay -= frame_time;
+ return frame_time;
+}