summaryrefslogtreecommitdiffstats
path: root/mpvcore/player/video.c
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2013-10-29 22:38:29 +0100
committerwm4 <wm4@nowhere>2013-10-30 01:53:53 +0100
commitb19414f3bf0ae28461ef6d5048e9626e69d45319 (patch)
tree585f81a52da4ff5a8c4f7cd52275040211994d7c /mpvcore/player/video.c
parenta84258d769a3d06958a017bb3fc47521ade5751b (diff)
downloadmpv-b19414f3bf0ae28461ef6d5048e9626e69d45319.tar.bz2
mpv-b19414f3bf0ae28461ef6d5048e9626e69d45319.tar.xz
Split mplayer.c
mplayer.c was a bit too big. Split it into multiple files. I hope the way it's split makes sense. Maybe some things don't make too much sense, or go against intuition. These will fixed as soon as I notice them. Some files are a bit questionable (misc.c, osd.c, configfiles.c), and suggestions how to organize this better are welcome. Regressions are possible due to reorganized include statements. Obviously I didn't just copy mplayer.c's orgy of include statements, but recreated them for each file. It's easily possible that there are oversights and mistakes, which will show up on other platforms. There is one actual change: the public avutil.h include is removed from encode.h, and I tried to replace most FFMIN/FFMAX/av_clip uses. I consider using libavutil too much as dangerous, because the set of include files they recursively pull in is rather arbitrary and is different between FFmpeg and Libav.
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;
+}