summaryrefslogtreecommitdiffstats
path: root/player/video.c
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2014-08-12 23:17:35 +0200
committerwm4 <wm4@nowhere>2014-08-12 23:24:08 +0200
commit5ed7bc6321119365dc1a5d330774e32ac6b6a54e (patch)
tree941486fbb0c2e21fc093e55b7cbc8dfe08a370c6 /player/video.c
parentdf58e822377af0a3802bba862de80eafaea732cb (diff)
downloadmpv-5ed7bc6321119365dc1a5d330774e32ac6b6a54e.tar.bz2
mpv-5ed7bc6321119365dc1a5d330774e32ac6b6a54e.tar.xz
video: fix and simplify video format changes and last frame display
The previous commit broke these things, and fixing them is separate in this commit in order to reduce the volume of changes. Move the image queue from the VO to the playback core. The image queue is a remnant of the old way how vdpau was implemented, and increasingly became more and more an artifact. In the end, it did only one thing: computing the duration of the current frame. This was done by taking the PTS difference between the current and the future frame. We keep this, but by moving it out of the VO, we don't have to special-case format changes anymore. This simplifies the code a lot. Since we need the queue to compute the duration only, a queue size larger than 2 makes no sense, and we can hardcode that. Also change how the last frame is handled. The last frame is a bit of a problem, because video timing works by showing one frame after another, which makes it a special case. Make the VO provide a function to notify us when the frame is done, instead. The frame duration is used for that. This is not perfect. For example, changing playback speed during the last frame doesn't update the end time. Pausing will not stop the clock that times the last frame. But I don't think this matters for such a corner case.
Diffstat (limited to 'player/video.c')
-rw-r--r--player/video.c194
1 files changed, 86 insertions, 108 deletions
diff --git a/player/video.c b/player/video.c
index 1ee8ec1203..31a863bc36 100644
--- a/player/video.c
+++ b/player/video.c
@@ -211,11 +211,13 @@ void reset_video_state(struct MPContext *mpctx)
if (mpctx->video_out)
vo_seek_reset(mpctx->video_out);
+ mp_image_unrefp(&mpctx->next_frame[0]);
+ mp_image_unrefp(&mpctx->next_frame[1]);
+
mpctx->delay = 0;
mpctx->time_frame = 0;
mpctx->video_next_pts = MP_NOPTS_VALUE;
mpctx->playing_last_frame = false;
- mpctx->last_frame_duration = 0;
mpctx->total_avsync_change = 0;
mpctx->drop_frame_cnt = 0;
mpctx->dropped_frames = 0;
@@ -481,18 +483,10 @@ static void init_vo(struct MPContext *mpctx)
mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL);
}
-// Fill the VO buffer with a newly filtered or decoded image.
+// Fill mpctx->next_frame[] with a newly filtered or decoded image.
// returns VD_* code
-static int video_output_image(struct MPContext *mpctx, double endpts,
- bool reconfig_ok)
+static int video_output_image(struct MPContext *mpctx, double endpts)
{
- struct vo *vo = mpctx->video_out;
-
- // Already enough video buffered in VO?
- // (This implies vo_has_next_frame(vo, false/true) returns true.)
- if (!vo_needs_new_image(vo) && vo->params)
- return 1;
-
// Filter a new frame.
int r = video_decode_and_filter(mpctx);
if (r < 0)
@@ -522,46 +516,12 @@ static int video_output_image(struct MPContext *mpctx, double endpts,
}
}
- // Filter output is different from VO input?
- bool need_vo_reconfig = !vo->params ||
- !mp_image_params_equal(&vf->output_params, vo->params);
-
- if (need_vo_reconfig) {
- // Draining VO buffers.
- if (vo_has_next_frame(vo, true))
- return 0; // EOF so that caller displays remaining VO frames
-
- // There was no decoded image yet - must not signal fake EOF.
- // Likewise, if there's no filtered frame yet, don't reconfig yet.
- if (!vf->output_params.imgfmt || !vf->output)
- return r;
-
- // Force draining.
- if (!reconfig_ok)
- return 0;
-
- struct mp_image_params p = vf->output_params;
-
- 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) {
- vf->initialized = -1;
- return VD_ERROR;
- }
- init_vo(mpctx);
- // Display the frame queued after this immediately.
- // (Neutralizes frame time calculation in update_video.)
- mpctx->video_next_pts = MP_NOPTS_VALUE;
- }
-
// Queue new frame, if there's one.
struct mp_image *img = vf_read_output_frame(vf);
if (img) {
- vo_queue_image(vo, img);
+ int pos = mpctx->next_frame[0] ? 1 : 0;
+ assert(!mpctx->next_frame[pos]);
+ mpctx->next_frame[pos] = img;
return VD_PROGRESS;
}
@@ -569,34 +529,40 @@ static int video_output_image(struct MPContext *mpctx, double endpts,
}
// returns VD_* code
-static int update_video(struct MPContext *mpctx, double endpts, bool reconfig_ok,
+static int update_video(struct MPContext *mpctx, double endpts,
double *frame_duration)
{
- struct vo *video_out = mpctx->video_out;
+ struct vo *vo = mpctx->video_out;
+ bool eof = false;
+
+ // Already enough video buffered?
+ bool vo_framedrop = !!mpctx->video_out->driver->flip_page_timed;
+ int min_frames = vo_framedrop ? 2 : 1; // framedrop needs duration
+ if (!mpctx->next_frame[min_frames - 1]) {
+ int r = video_output_image(mpctx, endpts);
+ if (r < 0 || r == VD_WAIT)
+ return r;
+ eof = r == VD_EOF;
+ }
if (mpctx->d_video->header->attached_picture) {
- if (video_out->hasframe)
+ if (vo_has_frame(vo))
return VD_EOF;
- if (vo_has_next_frame(video_out, true))
+ if (mpctx->next_frame[0]) {
+ mpctx->video_next_pts = MP_NOPTS_VALUE;
return VD_NEW_FRAME;
+ }
+ return VD_PROGRESS;
}
- int r = video_output_image(mpctx, endpts, reconfig_ok);
- if (r < 0 || r == VD_WAIT)
- return r;
+ // On EOF, we write the remaining frames; otherwise we must ensure that
+ // we have a frame (and with VO framedropping, the frame after that).
+ if (eof)
+ min_frames = 1;
+ if (!mpctx->next_frame[min_frames - 1])
+ return eof ? VD_EOF : VD_PROGRESS;
- // On EOF, we always drain the VO; otherwise we must ensure that
- // the VO will have enough frames buffered (matters especially for VO based
- // frame dropping).
- if (!vo_has_next_frame(video_out, r == VD_EOF))
- return r ? VD_PROGRESS : VD_EOF;
-
- if (mpctx->d_video->header->attached_picture) {
- mpctx->video_next_pts = MP_NOPTS_VALUE;
- return VD_NEW_FRAME;
- }
-
- double pts = vo_get_next_pts(video_out, 0);
+ double pts = mpctx->next_frame[0]->pts;
double last_pts = mpctx->video_next_pts;
if (last_pts == MP_NOPTS_VALUE)
last_pts = pts;
@@ -677,41 +643,19 @@ void write_video(struct MPContext *mpctx, double endpts)
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);
+ int r = update_video(mpctx, endpts, &frame_time);
+ MP_TRACE(mpctx, "update_video: %d\n", r);
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 < 0)
+ goto error;
- 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_EOF && vo_still_displaying(vo)) {
+ mpctx->video_status = STATUS_DRAINING;
+ return;
}
if (r == VD_NEW_FRAME) {
@@ -724,10 +668,6 @@ void write_video(struct MPContext *mpctx, double endpts)
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;
@@ -789,12 +729,28 @@ void write_video(struct MPContext *mpctx, double endpts)
mpctx->time_frame = 0;
}
- // last frame case
- // TODO: should be _after_ wait
- mpctx->playing_last_frame = false;
if (r != VD_NEW_FRAME)
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);
@@ -802,11 +758,16 @@ void write_video(struct MPContext *mpctx, double endpts)
return; // wait until VO wakes us up to get more frames
int64_t duration = -1;
- double vpts0 = vo_get_next_pts(vo, 0);
- double vpts1 = vo_get_next_pts(vo, 1);
- if (vpts0 != MP_NOPTS_VALUE && vpts1 != MP_NOPTS_VALUE) {
+ 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
- double diff = (vpts1 - vpts0) / opts->playback_speed;
+ diff /= opts->playback_speed;
if (mpctx->time_frame < 0)
diff += mpctx->time_frame;
duration = MPCLAMP(diff, 0, 10) * 1e6;
@@ -819,7 +780,10 @@ void write_video(struct MPContext *mpctx, double endpts)
update_subtitles(mpctx);
update_osd_msg(mpctx);
- vo_queue_frame(vo, pts, duration);
+ vo_queue_frame(vo, mpctx->next_frame[0], pts, duration);
+
+ mpctx->next_frame[0] = mpctx->next_frame[1];
+ mpctx->next_frame[1] = NULL;
// For print_status - VO call finishing early is OK for sync
mpctx->time_frame -= get_relative_time(mpctx);
@@ -849,4 +813,18 @@ void write_video(struct MPContext *mpctx, double endpts)
if (mpctx->max_frames > 0)
mpctx->max_frames--;
}
+
+ 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;
}