mirror of
https://github.com/mpv-player/mpv
synced 2025-03-11 08:37:59 +00:00
video: move video frame queue from vo_vdpau.c to vo.c
Remove the special casing of vo_vdpau vs. other VOs. Replace the complicated interaction between vo.c and vo_vdpau.c with a simple queue in vo.c. VOs other than vdpau are handled by setting the length of the queue to 1 (this is essentially what waiting_mpi was). Note that vo_vdpau.c seems to have buffered only 1 or 2 frames into the future, while the remaining 3 or 4 frames were past frames. So the new code buffers 2 frames (vo_vdpau.c requests this queue length by setting vo->max_video_queue to 2). It should probably be investigated why vo_vdpau.c kept so many past frames. The field vo->redrawing is removed. I'm not really sure what that would be needed for; it seems pointless. Future directions include making the interface between playloop and VO simpler, as well as making rendering a frame a single operation, as opposed to the weird 3-step sequence of rendering, drawing OSD, and flipping.
This commit is contained in:
parent
d6dc8642ae
commit
6775487a46
@ -112,6 +112,8 @@ const struct vo_driver *video_out_drivers[] =
|
||||
NULL
|
||||
};
|
||||
|
||||
static void forget_frames(struct vo *vo);
|
||||
|
||||
static bool get_desc(struct m_obj_desc *dst, int index)
|
||||
{
|
||||
if (index >= MP_ARRAY_SIZE(video_out_drivers) - 1)
|
||||
@ -171,6 +173,7 @@ static struct vo *vo_create(struct mpv_global *global,
|
||||
.input_ctx = input_ctx,
|
||||
.event_fd = -1,
|
||||
.monitor_par = 1,
|
||||
.max_video_queue = 1,
|
||||
.next_pts = MP_NOPTS_VALUE,
|
||||
.next_pts2 = MP_NOPTS_VALUE,
|
||||
};
|
||||
@ -228,7 +231,7 @@ void vo_destroy(struct vo *vo)
|
||||
{
|
||||
if (vo->event_fd != -1)
|
||||
mp_input_rm_key_fd(vo->input_ctx, vo->event_fd);
|
||||
mp_image_unrefp(&vo->waiting_mpi);
|
||||
forget_frames(vo);
|
||||
vo->driver->uninit(vo);
|
||||
talloc_free(vo);
|
||||
}
|
||||
@ -345,9 +348,7 @@ int vo_reconfig(struct vo *vo, struct mp_image_params *params, int flags)
|
||||
talloc_free(vo->params);
|
||||
vo->params = NULL;
|
||||
}
|
||||
vo->frame_loaded = false;
|
||||
vo->waiting_mpi = NULL;
|
||||
vo->redrawing = false;
|
||||
forget_frames(vo);
|
||||
vo->hasframe = false;
|
||||
return ret;
|
||||
}
|
||||
@ -357,19 +358,45 @@ int vo_control(struct vo *vo, uint32_t request, void *data)
|
||||
return vo->driver->control(vo, request, data);
|
||||
}
|
||||
|
||||
static void update_video_queue_state(struct vo *vo, bool eof)
|
||||
{
|
||||
int num = vo->num_video_queue;
|
||||
// Normally, buffer 1 image ahead, except if the queue is limited to less
|
||||
// than 2 entries, or if EOF is reached and there aren't enough images left.
|
||||
int min = 2;
|
||||
if (vo->max_video_queue < 2 || (vo->num_video_queue < 2 && eof))
|
||||
min = 1;
|
||||
vo->frame_loaded = num >= min;
|
||||
if (!vo->frame_loaded)
|
||||
num = -1;
|
||||
vo->next_pts = num > 0 ? vo->video_queue[0]->pts : MP_NOPTS_VALUE;
|
||||
vo->next_pts2 = num > 1 ? vo->video_queue[1]->pts : MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
static void forget_frames(struct vo *vo)
|
||||
{
|
||||
for (int n = 0; n < vo->num_video_queue; n++)
|
||||
talloc_free(vo->video_queue[n]);
|
||||
vo->num_video_queue = 0;
|
||||
update_video_queue_state(vo, false);
|
||||
}
|
||||
|
||||
void vo_queue_image(struct vo *vo, struct mp_image *mpi)
|
||||
{
|
||||
assert(mpi);
|
||||
if (!vo->config_ok)
|
||||
return;
|
||||
if (vo->driver->buffer_frames) {
|
||||
vo->driver->draw_image(vo, mpi);
|
||||
mpi = mp_image_new_ref(mpi);
|
||||
if (vo->driver->filter_image)
|
||||
mpi = vo->driver->filter_image(vo, mpi);
|
||||
if (!mpi) {
|
||||
MP_ERR(vo, "Could not upload image.\n");
|
||||
return;
|
||||
}
|
||||
vo->frame_loaded = true;
|
||||
vo->next_pts = mpi->pts;
|
||||
vo->next_pts2 = MP_NOPTS_VALUE;
|
||||
assert(!vo->waiting_mpi);
|
||||
vo->waiting_mpi = mp_image_new_ref(mpi);
|
||||
assert(vo->max_video_queue <= VO_MAX_QUEUE);
|
||||
assert(vo->num_video_queue < vo->max_video_queue);
|
||||
vo->video_queue[vo->num_video_queue++] = mpi;
|
||||
update_video_queue_state(vo, false);
|
||||
}
|
||||
|
||||
int vo_redraw_frame(struct vo *vo)
|
||||
@ -378,7 +405,6 @@ int vo_redraw_frame(struct vo *vo)
|
||||
return -1;
|
||||
if (vo_control(vo, VOCTRL_REDRAW_FRAME, NULL) == true) {
|
||||
vo->want_redraw = false;
|
||||
vo->redrawing = true;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
@ -395,32 +421,33 @@ int vo_get_buffered_frame(struct vo *vo, bool eof)
|
||||
{
|
||||
if (!vo->config_ok)
|
||||
return -1;
|
||||
if (vo->frame_loaded)
|
||||
return 0;
|
||||
if (!vo->driver->buffer_frames)
|
||||
return -1;
|
||||
vo->driver->get_buffered_frame(vo, eof);
|
||||
update_video_queue_state(vo, eof);
|
||||
return vo->frame_loaded ? 0 : -1;
|
||||
}
|
||||
|
||||
// Remove vo->video_queue[0]
|
||||
static void shift_queue(struct vo *vo)
|
||||
{
|
||||
if (!vo->num_video_queue)
|
||||
return;
|
||||
talloc_free(vo->video_queue[0]);
|
||||
vo->num_video_queue--;
|
||||
for (int n = 0; n < vo->num_video_queue; n++)
|
||||
vo->video_queue[n] = vo->video_queue[n + 1];
|
||||
}
|
||||
|
||||
void vo_skip_frame(struct vo *vo)
|
||||
{
|
||||
vo_control(vo, VOCTRL_SKIPFRAME, NULL);
|
||||
shift_queue(vo);
|
||||
vo->frame_loaded = false;
|
||||
mp_image_unrefp(&vo->waiting_mpi);
|
||||
}
|
||||
|
||||
void vo_new_frame_imminent(struct vo *vo)
|
||||
{
|
||||
if (vo->driver->buffer_frames)
|
||||
vo_control(vo, VOCTRL_NEWFRAME, NULL);
|
||||
else {
|
||||
assert(vo->frame_loaded);
|
||||
assert(vo->waiting_mpi);
|
||||
assert(vo->waiting_mpi->pts == vo->next_pts);
|
||||
vo->driver->draw_image(vo, vo->waiting_mpi);
|
||||
mp_image_unrefp(&vo->waiting_mpi);
|
||||
}
|
||||
assert(vo->frame_loaded);
|
||||
assert(vo->num_video_queue > 0);
|
||||
vo->driver->draw_image(vo, vo->video_queue[0]);
|
||||
shift_queue(vo);
|
||||
}
|
||||
|
||||
void vo_draw_osd(struct vo *vo, struct osd_state *osd)
|
||||
@ -433,18 +460,13 @@ void vo_flip_page(struct vo *vo, int64_t pts_us, int duration)
|
||||
{
|
||||
if (!vo->config_ok)
|
||||
return;
|
||||
if (!vo->redrawing) {
|
||||
vo->frame_loaded = false;
|
||||
vo->next_pts = MP_NOPTS_VALUE;
|
||||
vo->next_pts2 = MP_NOPTS_VALUE;
|
||||
}
|
||||
vo->want_redraw = false;
|
||||
vo->redrawing = false;
|
||||
if (vo->driver->flip_page_timed)
|
||||
vo->driver->flip_page_timed(vo, pts_us, duration);
|
||||
else
|
||||
vo->driver->flip_page(vo);
|
||||
vo->hasframe = true;
|
||||
update_video_queue_state(vo, false);
|
||||
}
|
||||
|
||||
void vo_check_events(struct vo *vo)
|
||||
@ -457,11 +479,8 @@ void vo_check_events(struct vo *vo)
|
||||
void vo_seek_reset(struct vo *vo)
|
||||
{
|
||||
vo_control(vo, VOCTRL_RESET, NULL);
|
||||
vo->frame_loaded = false;
|
||||
vo->next_pts = MP_NOPTS_VALUE;
|
||||
vo->next_pts2 = MP_NOPTS_VALUE;
|
||||
forget_frames(vo);
|
||||
vo->hasframe = false;
|
||||
mp_image_unrefp(&vo->waiting_mpi);
|
||||
}
|
||||
|
||||
// Calculate the appropriate source and destination rectangle to
|
||||
|
@ -54,8 +54,6 @@ enum mp_voctrl {
|
||||
/* for hardware decoding */
|
||||
VOCTRL_GET_HWDEC_INFO, // struct mp_hwdec_info*
|
||||
|
||||
VOCTRL_NEWFRAME,
|
||||
VOCTRL_SKIPFRAME,
|
||||
VOCTRL_REDRAW_FRAME,
|
||||
|
||||
VOCTRL_ONTOP,
|
||||
@ -135,16 +133,14 @@ struct voctrl_screenshot_args {
|
||||
// VO does handle mp_image_params.rotate in 90 degree steps
|
||||
#define VO_CAP_ROTATE90 1
|
||||
|
||||
#define VO_MAX_QUEUE 5
|
||||
|
||||
struct vo;
|
||||
struct osd_state;
|
||||
struct mp_image;
|
||||
struct mp_image_params;
|
||||
|
||||
struct vo_driver {
|
||||
// Driver buffers or adds (deinterlace) frames and will keep track
|
||||
// of pts values itself
|
||||
bool buffer_frames;
|
||||
|
||||
// Encoding functionality, which can be invoked via --o only.
|
||||
bool encode;
|
||||
|
||||
@ -166,6 +162,20 @@ struct vo_driver {
|
||||
*/
|
||||
int (*query_format)(struct vo *vo, uint32_t format);
|
||||
|
||||
/*
|
||||
* Optional. Can be used to convert the input image into something VO
|
||||
* internal, such as GPU surfaces. Ownership of mpi is passed to the
|
||||
* function, and the returned image is owned by the caller.
|
||||
* The following guarantees are given:
|
||||
* - mpi has the format with which the VO was configured
|
||||
* - the returned image can be arbitrary, and the VO merely manages its
|
||||
* lifetime
|
||||
* - images passed to draw_image are always passed through this function
|
||||
* - the maximum number of images kept alive is not over vo->max_video_queue
|
||||
* - if vo->max_video_queue is large enough, some images may be buffered ahead
|
||||
*/
|
||||
struct mp_image *(*filter_image)(struct vo *vo, struct mp_image *mpi);
|
||||
|
||||
/*
|
||||
* Initialize or reconfigure the display driver.
|
||||
* params: video parameters, like pixel format and frame size
|
||||
@ -187,14 +197,6 @@ struct vo_driver {
|
||||
*/
|
||||
void (*draw_image)(struct vo *vo, struct mp_image *mpi);
|
||||
|
||||
/*
|
||||
* Get extra frames from the VO, such as those added by VDPAU
|
||||
* deinterlace. Preparing the next such frame if any could be done
|
||||
* automatically by the VO after a previous flip_page(), but having
|
||||
* it as a separate step seems to allow making code more robust.
|
||||
*/
|
||||
void (*get_buffered_frame)(struct vo *vo, bool eof);
|
||||
|
||||
/*
|
||||
* Draws OSD to the screen buffer
|
||||
*/
|
||||
@ -236,15 +238,18 @@ struct vo {
|
||||
bool untimed; // non-interactive, don't do sleep calls in playloop
|
||||
|
||||
bool frame_loaded; // Is there a next frame the VO could flip to?
|
||||
struct mp_image *waiting_mpi;
|
||||
double next_pts; // pts value of the next frame if any
|
||||
double next_pts2; // optional pts of frame after that
|
||||
bool want_redraw; // visible frame wrong (window resize), needs refresh
|
||||
bool redrawing; // between redrawing frame and flipping it
|
||||
bool hasframe; // >= 1 frame has been drawn, so redraw is possible
|
||||
double wakeup_period; // if > 0, this sets the maximum wakeup period for event polling
|
||||
|
||||
double flip_queue_offset; // queue flip events at most this much in advance
|
||||
int max_video_queue; // queue this many decoded video frames (<=VO_MAX_QUEUE)
|
||||
|
||||
// Frames to display; the next (i.e. oldest, lowest PTS) image has index 0.
|
||||
struct mp_image *video_queue[VO_MAX_QUEUE];
|
||||
int num_video_queue;
|
||||
|
||||
const struct vo_driver *driver;
|
||||
void *priv;
|
||||
|
@ -86,11 +86,8 @@ struct vdpctx {
|
||||
VdpOutputSurface rgb_surfaces[NUM_BUFFERED_VIDEO];
|
||||
bool rgb_surfaces_used[NUM_BUFFERED_VIDEO];
|
||||
VdpOutputSurface black_pixel;
|
||||
struct mp_image *buffered_video[NUM_BUFFERED_VIDEO];
|
||||
int buffer_pos;
|
||||
|
||||
// State for redrawing the screen after seek-reset
|
||||
int prev_buffer_pos;
|
||||
struct mp_image *current_image;
|
||||
|
||||
int output_surface_width, output_surface_height;
|
||||
|
||||
@ -161,12 +158,9 @@ static int render_video_to_output_surface(struct vo *vo,
|
||||
struct vdp_functions *vdp = vc->vdp;
|
||||
VdpTime dummy;
|
||||
VdpStatus vdp_st;
|
||||
int dp = vc->buffer_pos;
|
||||
struct mp_image *mpi = vc->current_image;
|
||||
|
||||
// Redraw frame from before seek reset?
|
||||
if (dp < 0)
|
||||
dp = vc->prev_buffer_pos;
|
||||
if (dp < 0) {
|
||||
if (!mpi) {
|
||||
// At least clear the screen if there is nothing to render
|
||||
int flags = VDP_OUTPUT_SURFACE_RENDER_ROTATE_0;
|
||||
vdp_st = vdp->output_surface_render_output_surface(output_surface,
|
||||
@ -176,8 +170,6 @@ static int render_video_to_output_surface(struct vo *vo,
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct mp_image *mpi = vc->buffered_video[dp];
|
||||
|
||||
vdp_st = vdp->presentation_queue_block_until_surface_idle(vc->flip_queue,
|
||||
output_surface,
|
||||
&dummy);
|
||||
@ -230,73 +222,14 @@ static int video_to_output_surface(struct vo *vo)
|
||||
&vc->out_rect_vid, &vc->src_rect_vid);
|
||||
}
|
||||
|
||||
static int next_buffer_pos(struct vo *vo, bool eof)
|
||||
{
|
||||
struct vdpctx *vc = vo->priv;
|
||||
|
||||
int dqp = vc->buffer_pos;
|
||||
if (dqp < 0)
|
||||
dqp += 1000;
|
||||
else
|
||||
dqp -= 1;
|
||||
if (dqp < (eof ? 0 : 1))
|
||||
return -1;
|
||||
return dqp;
|
||||
}
|
||||
|
||||
static void set_next_frame_info(struct vo *vo, bool eof)
|
||||
{
|
||||
struct vdpctx *vc = vo->priv;
|
||||
|
||||
vo->frame_loaded = false;
|
||||
int dqp = next_buffer_pos(vo, eof);
|
||||
if (dqp < 0)
|
||||
return;
|
||||
vo->frame_loaded = true;
|
||||
|
||||
// Set pts values
|
||||
struct mp_image **bv = &vc->buffered_video[0];
|
||||
if (dqp == 0) { // no future frame/pts available
|
||||
vo->next_pts = bv[0]->pts;
|
||||
vo->next_pts2 = MP_NOPTS_VALUE;
|
||||
} else {
|
||||
vo->next_pts = bv[dqp]->pts;
|
||||
vo->next_pts2 = bv[dqp - 1]->pts;
|
||||
}
|
||||
}
|
||||
|
||||
static void add_new_video_surface(struct vo *vo, struct mp_image *mpi)
|
||||
{
|
||||
struct vdpctx *vc = vo->priv;
|
||||
struct mp_image **bv = vc->buffered_video;
|
||||
|
||||
mp_image_unrefp(&bv[NUM_BUFFERED_VIDEO - 1]);
|
||||
|
||||
for (int i = NUM_BUFFERED_VIDEO - 1; i > 0; i--)
|
||||
bv[i] = bv[i - 1];
|
||||
bv[0] = mpi;
|
||||
|
||||
vc->buffer_pos = FFMIN(vc->buffer_pos + 1, NUM_BUFFERED_VIDEO - 2);
|
||||
set_next_frame_info(vo, false);
|
||||
}
|
||||
|
||||
static void forget_frames(struct vo *vo, bool seek_reset)
|
||||
{
|
||||
struct vdpctx *vc = vo->priv;
|
||||
|
||||
if (seek_reset) {
|
||||
if (vc->buffer_pos >= 0)
|
||||
vc->prev_buffer_pos = vc->buffer_pos;
|
||||
} else {
|
||||
vc->prev_buffer_pos = -1001;
|
||||
}
|
||||
if (!seek_reset)
|
||||
mp_image_unrefp(&vc->current_image);
|
||||
|
||||
vc->buffer_pos = -1001;
|
||||
vc->dropped_frame = false;
|
||||
if (vc->prev_buffer_pos < 0) {
|
||||
for (int i = 0; i < NUM_BUFFERED_VIDEO; i++)
|
||||
mp_image_unrefp(&vc->buffered_video[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void resize(struct vo *vo)
|
||||
@ -975,23 +908,29 @@ static struct mp_image *get_rgb_surface(struct vo *vo)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void draw_image(struct vo *vo, mp_image_t *mpi)
|
||||
static void draw_image(struct vo *vo, struct mp_image *mpi)
|
||||
{
|
||||
struct vdpctx *vc = vo->priv;
|
||||
|
||||
mp_image_setrefp(&vc->current_image, mpi);
|
||||
|
||||
if (status_ok(vo))
|
||||
video_to_output_surface(vo);
|
||||
}
|
||||
|
||||
static struct mp_image *filter_image(struct vo *vo, struct mp_image *mpi)
|
||||
{
|
||||
struct vdpctx *vc = vo->priv;
|
||||
struct vdp_functions *vdp = vc->vdp;
|
||||
struct mp_image *reserved_mpi = NULL;
|
||||
VdpStatus vdp_st;
|
||||
|
||||
// Forget previous frames, as we can display a new one now.
|
||||
vc->prev_buffer_pos = -1001;
|
||||
mp_image_unrefp(&vc->buffered_video[NUM_BUFFERED_VIDEO - 1]);
|
||||
|
||||
if (vc->image_format == IMGFMT_VDPAU) {
|
||||
reserved_mpi = mp_image_new_ref(mpi);
|
||||
} else if (vc->rgb_mode) {
|
||||
reserved_mpi = get_rgb_surface(vo);
|
||||
if (!reserved_mpi)
|
||||
return;
|
||||
goto end;
|
||||
VdpOutputSurface rgb_surface = (uintptr_t)reserved_mpi->planes[0];
|
||||
if (rgb_surface != VDP_INVALID_HANDLE) {
|
||||
vdp_st = vdp->output_surface_put_bits_native(rgb_surface,
|
||||
@ -1005,7 +944,7 @@ static void draw_image(struct vo *vo, mp_image_t *mpi)
|
||||
reserved_mpi = mp_vdpau_get_video_surface(vc->mpvdp, vc->vdp_chroma_type,
|
||||
mpi->w, mpi->h);
|
||||
if (!reserved_mpi)
|
||||
return;
|
||||
goto end;
|
||||
VdpVideoSurface surface = (VdpVideoSurface)(intptr_t)reserved_mpi->planes[3];
|
||||
if (handle_preemption(vo) >= 0) {
|
||||
const void *destdata[3] = {mpi->planes[0], mpi->planes[2],
|
||||
@ -1020,9 +959,10 @@ static void draw_image(struct vo *vo, mp_image_t *mpi)
|
||||
}
|
||||
|
||||
reserved_mpi->pts = mpi->pts;
|
||||
add_new_video_surface(vo, reserved_mpi);
|
||||
|
||||
return;
|
||||
end:
|
||||
talloc_free(mpi);
|
||||
return reserved_mpi;
|
||||
}
|
||||
|
||||
// warning: the size and pixel format of surface must match that of the
|
||||
@ -1175,6 +1115,8 @@ static int preinit(struct vo *vo)
|
||||
|
||||
vc->video_eq.capabilities = MP_CSP_EQ_CAPS_COLORMATRIX;
|
||||
|
||||
vo->max_video_queue = 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1248,14 +1190,6 @@ static int control(struct vo *vo, uint32_t request, void *data)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case VOCTRL_NEWFRAME:
|
||||
vc->buffer_pos = next_buffer_pos(vo, true);
|
||||
if (status_ok(vo))
|
||||
video_to_output_surface(vo);
|
||||
return true;
|
||||
case VOCTRL_SKIPFRAME:
|
||||
vc->buffer_pos = next_buffer_pos(vo, true);
|
||||
return true;
|
||||
case VOCTRL_REDRAW_FRAME:
|
||||
if (status_ok(vo))
|
||||
video_to_output_surface(vo);
|
||||
@ -1293,7 +1227,6 @@ static int control(struct vo *vo, uint32_t request, void *data)
|
||||
#define OPT_BASE_STRUCT struct vdpctx
|
||||
|
||||
const struct vo_driver video_out_vdpau = {
|
||||
.buffer_frames = true,
|
||||
.description = "VDPAU with X11",
|
||||
.name = "vdpau",
|
||||
.preinit = preinit,
|
||||
@ -1301,7 +1234,7 @@ const struct vo_driver video_out_vdpau = {
|
||||
.reconfig = reconfig,
|
||||
.control = control,
|
||||
.draw_image = draw_image,
|
||||
.get_buffered_frame = set_next_frame_info,
|
||||
.filter_image = filter_image,
|
||||
.draw_osd = draw_osd,
|
||||
.flip_page_timed = flip_page_timed,
|
||||
.uninit = uninit,
|
||||
|
Loading…
Reference in New Issue
Block a user