[vlc-devel] [PATCH 12/48] vdpau: presentation queue video output display plugin

Rémi Denis-Courmont remi at remlab.net
Tue Jul 2 19:51:38 CEST 2013


This uses a VDPAU presentation queue to render VDPAU output surfaces
(i.e. screen-scaled RGBA pictures) on an X11 window. It is assumed that
a video filter will eventually convert pictures in the source video
format to VDPAU output surfaces of adequate dimensions.

The common VLC XCB event handling is used for X11 matters.
---
 modules/hw/vdpau/Makefile.am |  11 +
 modules/hw/vdpau/display.c   | 548 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 559 insertions(+)
 create mode 100644 modules/hw/vdpau/display.c

diff --git a/modules/hw/vdpau/Makefile.am b/modules/hw/vdpau/Makefile.am
index 92da18c..1018b29 100644
--- a/modules/hw/vdpau/Makefile.am
+++ b/modules/hw/vdpau/Makefile.am
@@ -30,3 +30,14 @@ libvdpau_chroma_plugin_la_SOURCES = chroma.c
 libvdpau_chroma_plugin_la_CFLAGS = $(AM_CFLAGS) # dummy
 libvdpau_chroma_plugin_la_LIBADD = $(AM_LIBADD)
 libvlc_LTLIBRARIES += libvdpau_chroma_plugin.la
+
+libvdpau_display_plugin_la_SOURCES = \
+	../../video_output/xcb/events.c \
+	display.c
+libvdpau_display_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) \
+	-I$(srcdir)/../../video_output/xcb
+libvdpau_display_plugin_la_CFLAGS = $(AM_CFLAGS) \
+	$(XCB_CFLAGS)
+libvdpau_display_plugin_la_LIBADD = $(AM_LIBADD) \
+	$(X_LIBS) $(X_PRE_LIBS) -lX11 $(XCB_LIBS)
+libvlc_LTLIBRARIES += libvdpau_display_plugin.la
diff --git a/modules/hw/vdpau/display.c b/modules/hw/vdpau/display.c
new file mode 100644
index 0000000..2d3ac97
--- /dev/null
+++ b/modules/hw/vdpau/display.c
@@ -0,0 +1,548 @@
+/**
+ * @file display.c
+ * @brief VDPAU video display module for VLC media player
+ */
+/*****************************************************************************
+ * Copyright © 2009-2013 Rémi Denis-Courmont
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <assert.h>
+
+#include <xcb/xcb.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_vout_display.h>
+#include <vlc_picture_pool.h>
+#include <vlc_xlib.h>
+
+#include "vlc_vdpau.h"
+#include "events.h"
+
+static int Open(vlc_object_t *);
+static void Close(vlc_object_t *);
+
+vlc_module_begin()
+    set_shortname(N_("VDPAU"))
+    set_description(N_("VDPAU output"))
+    set_category(CAT_VIDEO)
+    set_subcategory(SUBCAT_VIDEO_VOUT)
+    set_capability("vout display", 300)
+    set_callbacks(Open, Close)
+
+    add_shortcut("vdpau", "xid")
+vlc_module_end()
+
+#define MAX_PICTURES (32)
+
+struct vout_display_sys_t
+{
+    xcb_connection_t *conn; /**< XCB connection */
+    vout_window_t *embed; /**< parent window */
+    vdp_t *vdp; /**< VDPAU back-end */
+    picture_t *current; /**< Currently visible picture */
+
+    xcb_window_t window; /**< target window (owned by VDPAU back-end) */
+    xcb_cursor_t cursor; /**< blank cursor */
+    VdpDevice device; /**< VDPAU device handle */
+    VdpPresentationQueueTarget target; /**< VDPAU presentation queue target */
+    VdpPresentationQueue queue; /**< VDPAU presentation queue */
+    VdpRGBAFormat rgb_fmt; /**< Output surface format */
+
+    picture_pool_t *pool; /**< pictures pool */
+    picture_sys_t *pics[MAX_PICTURES];
+};
+
+static picture_pool_t *PoolAlloc(vout_display_t *vd, unsigned requested_count)
+{
+    vout_display_sys_t *sys = vd->sys;
+    const video_format_t *fmt = &vd->fmt;
+    picture_t *pics[MAX_PICTURES];
+    picture_resource_t res = { .p_sys = NULL };
+
+    if (requested_count > MAX_PICTURES)
+        requested_count = MAX_PICTURES;
+
+    unsigned count = 0;
+    while (count < requested_count)
+    {
+        picture_sys_t *psys = malloc(sizeof (*psys));
+        if (unlikely(psys == NULL))
+            break;
+
+        VdpStatus err = vdp_output_surface_create(sys->vdp, sys->device,
+                     sys->rgb_fmt, fmt->i_visible_width, fmt->i_visible_height,
+                                                  &psys->surface);
+        if (err != VDP_STATUS_OK)
+        {
+            free(psys);
+            msg_Err(vd, "%s creation failure: %s", "output surface",
+                    vdp_get_error_string(sys->vdp, err));
+            break;
+        }
+        psys->device = sys->device;
+        psys->vdp = sys->vdp;
+
+        res.p_sys = psys;
+        pics[count] = picture_NewFromResource(&vd->fmt, &res);
+        if (pics[count] == NULL)
+        {
+            free(psys);
+            break;
+        }
+        sys->pics[count++] = res.p_sys;
+    }
+    sys->current = NULL;
+    return count ? picture_pool_New(count, pics) : NULL;
+}
+
+static void PoolFree(vout_display_t *vd, picture_pool_t *pool)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    for (unsigned count = 0; count < MAX_PICTURES; count++)
+    {
+        picture_sys_t *psys = sys->pics[count];
+        if (psys == NULL)
+            continue;
+
+        VdpStatus err = vdp_output_surface_destroy(sys->vdp, psys->surface);
+        if (err != VDP_STATUS_OK)
+            msg_Err(vd, "%s destruction failure: %s", "output surface",
+                    vdp_get_error_string(sys->vdp, err));
+    }
+    if (sys->current != NULL)
+        picture_Release(sys->current);
+    picture_pool_Delete(pool);
+}
+
+static picture_pool_t *Pool(vout_display_t *vd, unsigned requested_count)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    if (sys->pool == NULL)
+        sys->pool = PoolAlloc(vd, requested_count);
+    return sys->pool;
+}
+
+static void Queue(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
+{
+    vout_display_sys_t *sys = vd->sys;
+    VdpOutputSurface surface = pic->p_sys->surface;
+    VdpStatus err;
+
+    (void) subpicture;
+
+    /* Compute picture presentation time */
+    mtime_t now = mdate();
+    VdpTime pts;
+
+    err = vdp_presentation_queue_get_time(sys->vdp, sys->queue, &pts);
+    if (err != VDP_STATUS_OK)
+    {
+        msg_Err(vd, "presentation queue time failure: %s",
+                vdp_get_error_string(sys->vdp, err));
+        return;
+    }
+
+    mtime_t delay = pic->date - now;
+    if (delay < 0)
+        delay = 0; /* core bug: date is not updated during pause */
+    if (unlikely(delay > CLOCK_FREQ))
+    {   /* We would get stuck if the delay was too long. */
+        msg_Dbg(vd, "picture date corrupt: delay of %"PRId64" us", delay);
+        delay = CLOCK_FREQ / 50;
+    }
+    pts += delay * 1000;
+
+    /* Queue picture */
+    err = vdp_presentation_queue_display(sys->vdp, sys->queue, surface, 0, 0,
+                                         pts);
+    if (err != VDP_STATUS_OK)
+        msg_Err(vd, "presentation queue display failure: %s",
+                vdp_get_error_string(sys->vdp, err));
+}
+
+static void Wait(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    picture_t *current = sys->current;
+    if (current != NULL)
+    {
+        picture_sys_t *psys = current->p_sys;
+        VdpTime pts;
+        VdpStatus err;
+
+        err = vdp_presentation_queue_block_until_surface_idle(sys->vdp,
+                                              sys->queue, psys->surface, &pts);
+        if (err != VDP_STATUS_OK)
+        {
+            msg_Err(vd, "presentation queue blocking error: %s",
+                    vdp_get_error_string(sys->vdp, err));
+            picture_Release(pic);
+            return;
+        }
+        picture_Release(current);
+    }
+
+    sys->current = pic;
+    (void) subpicture;
+}
+
+static int Control(vout_display_t *vd, int query, va_list ap)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    switch (query)
+    {
+    case VOUT_DISPLAY_HIDE_MOUSE:
+        xcb_change_window_attributes(sys->conn, sys->embed->handle.xid,
+                                    XCB_CW_CURSOR, &(uint32_t){ sys->cursor });
+        break;
+    case VOUT_DISPLAY_RESET_PICTURES:
+    {
+        msg_Dbg(vd, "resetting pictures");
+        if (sys->pool != NULL)
+        {
+            PoolFree(vd, sys->pool);
+            sys->pool = NULL;
+        }
+
+        const video_format_t *src= &vd->source;
+        video_format_t *fmt = &vd->fmt;
+        vout_display_place_t place;
+
+        vout_display_PlacePicture(&place, src, vd->cfg, false);
+
+        fmt->i_width = src->i_width * place.width / src->i_visible_width;
+        fmt->i_height = src->i_height * place.height / src->i_visible_height;
+        fmt->i_visible_width  = place.width;
+        fmt->i_visible_height = place.height;
+        fmt->i_x_offset = src->i_x_offset * place.width / src->i_visible_width;
+        fmt->i_y_offset = src->i_y_offset * place.height / src->i_visible_height;
+
+        const uint32_t values[] = { place.x, place.y,
+                                    place.width, place.height, };
+        xcb_configure_window(sys->conn, sys->window,
+                             XCB_CONFIG_WINDOW_X|XCB_CONFIG_WINDOW_Y|
+                             XCB_CONFIG_WINDOW_WIDTH|XCB_CONFIG_WINDOW_HEIGHT,
+                             values);
+        break;
+    }
+    case VOUT_DISPLAY_CHANGE_FULLSCREEN:
+    {
+        const vout_display_cfg_t *c = va_arg(ap, const vout_display_cfg_t *);
+        return vout_window_SetFullScreen(sys->embed, c->is_fullscreen);
+    }
+    case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
+    {
+        unsigned state = va_arg(ap, unsigned);
+        return vout_window_SetState(sys->embed, state);
+    }
+    case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
+    {
+        const vout_display_cfg_t *cfg = va_arg(ap, const vout_display_cfg_t *);
+        bool forced = va_arg(ap, int);
+        if (forced)
+        {
+            vout_window_SetSize(sys->embed,
+                                cfg->display.width, cfg->display.height);
+            return VLC_EGENERIC; /* Always fail. See x11.c for rationale. */
+        }
+
+        vout_display_place_t place;
+        vout_display_PlacePicture(&place, &vd->source, cfg, false);
+        if (place.width  != vd->fmt.i_visible_width
+         || place.height != vd->fmt.i_visible_height)
+        {
+            vout_display_SendEventPicturesInvalid (vd);
+            return VLC_SUCCESS;
+        }
+
+        const uint32_t values[] = { place.x, place.y,
+                                    place.width, place.height, };
+        xcb_configure_window(sys->conn, sys->window,
+                             XCB_CONFIG_WINDOW_X|XCB_CONFIG_WINDOW_Y|
+                             XCB_CONFIG_WINDOW_WIDTH|XCB_CONFIG_WINDOW_HEIGHT,
+                             values);
+        break;
+    }
+    case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
+    case VOUT_DISPLAY_CHANGE_ZOOM:
+    case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
+    case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
+        vout_display_SendEventPicturesInvalid (vd);
+        return VLC_SUCCESS;
+    default:
+        msg_Err(vd, "unknown control request %d", query);
+        return VLC_EGENERIC;
+    }
+    xcb_flush (sys->conn);
+    return VLC_SUCCESS;
+}
+
+static void Manage(vout_display_t *vd)
+{
+    vout_display_sys_t *sys = vd->sys;
+    bool visible;
+
+    XCB_Manage(vd, sys->conn, &visible);
+}
+
+static int xcb_screen_num(xcb_connection_t *conn, const xcb_screen_t *screen)
+{
+    const xcb_setup_t *setup = xcb_get_setup(conn);
+    unsigned snum = 0;
+
+    for (xcb_screen_iterator_t i = xcb_setup_roots_iterator(setup);
+         i.rem > 0; xcb_screen_next(&i))
+    {
+        if (i.data->root == screen->root)
+            return snum;
+        snum++;
+    }
+    return -1;
+}
+
+static int Open(vlc_object_t *obj)
+{
+    if (!vlc_xlib_init(obj))
+        return VLC_EGENERIC;
+
+    vout_display_t *vd = (vout_display_t *)obj;
+    vout_display_sys_t *sys = malloc(sizeof (*sys));
+    if (unlikely(sys == NULL))
+        return VLC_ENOMEM;
+
+    const xcb_screen_t *screen;
+    uint16_t width, height;
+    sys->embed = XCB_parent_Create(vd, &sys->conn, &screen, &width, &height);
+    if (sys->embed == NULL)
+    {
+        free(sys);
+        return VLC_EGENERIC;
+    }
+
+    /* Load the VDPAU back-end and create a device instance */
+    VdpStatus err = vdp_get_x11(sys->embed->display.x11,
+                                xcb_screen_num(sys->conn, screen),
+                                &sys->vdp, &sys->device);
+    if (err != VDP_STATUS_OK)
+    {
+        msg_Dbg(obj, "device creation failure: error %d", (int)err);
+        xcb_disconnect(sys->conn);
+        vout_display_DeleteWindow(vd, sys->embed);
+        free(sys);
+        return VLC_EGENERIC;
+    }
+
+    const char *info;
+    if (vdp_get_information_string(sys->vdp, &info) == VDP_STATUS_OK)
+        msg_Dbg(vd, "using back-end %s", info);
+
+    /* Check source format */
+    VdpChromaType chroma;
+    VdpYCbCrFormat format;
+    if (vd->fmt.i_chroma == VLC_CODEC_VDPAU_VIDEO_420
+     || vd->fmt.i_chroma == VLC_CODEC_VDPAU_VIDEO_422
+     || vd->fmt.i_chroma == VLC_CODEC_VDPAU_OUTPUT)
+        ;
+    else
+    if (vlc_fourcc_to_vdp_ycc(vd->fmt.i_chroma, &chroma, &format))
+    {
+        uint32_t w, h;
+        VdpBool ok;
+
+        err = vdp_video_surface_query_capabilities(sys->vdp, sys->device,
+                                                   chroma, &ok, &w, &h);
+        if (err != VDP_STATUS_OK)
+        {
+            msg_Err(vd, "%s capabilities query failure: %s", "video surface",
+                    vdp_get_error_string(sys->vdp, err));
+            goto error;
+        }
+        if (!ok || w < vd->fmt.i_width || h < vd->fmt.i_height)
+        {
+            msg_Err(vd, "source video %s not supported", "chroma type");
+            goto error;
+        }
+
+        err = vdp_video_surface_query_get_put_bits_y_cb_cr_capabilities(
+                                   sys->vdp, sys->device, chroma, format, &ok);
+        if (err != VDP_STATUS_OK)
+        {
+            msg_Err(vd, "%s capabilities query failure: %s", "video surface",
+                    vdp_get_error_string(sys->vdp, err));
+            goto error;
+        }
+        if (!ok)
+        {
+            msg_Err(vd, "source video %s not supported", "YCbCr format");
+            goto error;
+        }
+    }
+    else
+        goto error;
+    /* TODO: check video mixer capabilities separately? */
+
+    /* Select surface format */
+    static const VdpRGBAFormat rgb_fmts[] = {
+        VDP_RGBA_FORMAT_R10G10B10A2, VDP_RGBA_FORMAT_B10G10R10A2,
+        VDP_RGBA_FORMAT_B8G8R8A8, VDP_RGBA_FORMAT_R8G8B8A8,
+    };
+    unsigned i;
+
+    for (i = 0; i < sizeof (rgb_fmts) / sizeof (rgb_fmts[0]); i++)
+    {
+        uint32_t w, h;
+        VdpBool ok;
+
+        err = vdp_output_surface_query_capabilities(sys->vdp, sys->device,
+                                                    rgb_fmts[i], &ok, &w, &h);
+        if (err != VDP_STATUS_OK)
+        {
+            msg_Err(vd, "%s capabilities query failure: %s", "output surface",
+                    vdp_get_error_string(sys->vdp, err));
+            continue;
+        }
+        /* NOTE: Wrong! No warranties that zoom <= 100%! */
+        if (!ok || w < vd->fmt.i_width || h < vd->fmt.i_height)
+            continue;
+
+        sys->rgb_fmt = rgb_fmts[i];
+        msg_Dbg(vd, "using RGBA format %u", sys->rgb_fmt);
+        break;
+    }
+    if (i == sizeof (rgb_fmts) / sizeof (rgb_fmts[0]))
+    {
+        msg_Err(vd, "no supported output surface format");
+        goto error;
+    }
+
+    /* VDPAU-X11 requires a window dedicated to the back-end */
+    {
+        xcb_pixmap_t pix = xcb_generate_id(sys->conn);
+        xcb_create_pixmap(sys->conn, screen->root_depth, pix,
+                          screen->root, 1, 1);
+
+        uint32_t mask =
+            XCB_CW_BACK_PIXMAP | XCB_CW_BACK_PIXEL |
+            XCB_CW_BORDER_PIXMAP | XCB_CW_BORDER_PIXEL |
+            XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
+        const uint32_t values[] = {
+            pix, screen->black_pixel, pix, screen->black_pixel,
+            XCB_EVENT_MASK_VISIBILITY_CHANGE, screen->default_colormap
+        };
+        vout_display_place_t place;
+
+        vout_display_PlacePicture (&place, &vd->source, vd->cfg, false);
+        sys->window = xcb_generate_id(sys->conn);
+
+        xcb_void_cookie_t c =
+            xcb_create_window_checked(sys->conn, screen->root_depth,
+                sys->window, sys->embed->handle.xid, place.x, place.y,
+                place.width, place.height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
+                screen->root_visual, mask, values);
+        if (XCB_error_Check(vd, sys->conn, "window creation failure", c))
+            goto error;
+        msg_Dbg(vd, "using X11 window 0x%08"PRIx32, sys->window);
+        xcb_map_window(sys->conn, sys->window);
+    }
+
+    /* Initialize VDPAU queue */
+    err = vdp_presentation_queue_target_create_x11(sys->vdp, sys->device,
+                                                   sys->window, &sys->target);
+    if (err != VDP_STATUS_OK)
+    {
+        msg_Err(vd, "%s creation failure: %s", "presentation queue target",
+                vdp_get_error_string(sys->vdp, err));
+        goto error;
+    }
+
+    err = vdp_presentation_queue_create(sys->vdp, sys->device, sys->target,
+                                        &sys->queue);
+    if (err != VDP_STATUS_OK)
+    {
+        msg_Err(vd, "%s creation failure: %s", "presentation queue",
+                vdp_get_error_string(sys->vdp, err));
+        goto error;
+    }
+
+    VdpColor black = { 0.f, 0.f, 0.f, 1.f };
+    vdp_presentation_queue_set_background_color(sys->vdp, sys->queue, &black);
+
+    sys->cursor = XCB_cursor_Create(sys->conn, screen);
+    sys->pool = NULL;
+    for (unsigned count = 0; count < MAX_PICTURES; count++)
+        sys->pics[count] = NULL;
+
+    /* */
+    vd->sys = sys;
+    vd->info.has_pictures_invalid = true;
+    vd->info.has_event_thread = true;
+    vd->fmt.i_chroma = VLC_CODEC_VDPAU_OUTPUT;
+
+    vd->pool = Pool;
+    vd->prepare = Queue;
+    vd->display = Wait;
+    vd->control = Control;
+    vd->manage = Manage;
+
+    /* */
+    bool is_fullscreen = vd->cfg->is_fullscreen;
+    if (is_fullscreen && vout_window_SetFullScreen(sys->embed, true))
+        is_fullscreen = false;
+    vout_display_SendEventFullscreen(vd, is_fullscreen);
+    vout_display_SendEventDisplaySize(vd, width, height, is_fullscreen);
+
+    return VLC_SUCCESS;
+
+error:
+    vdp_release_x11(sys->vdp);
+    xcb_disconnect(sys->conn);
+    vout_display_DeleteWindow(vd, sys->embed);
+    free(sys);
+    return VLC_EGENERIC;
+}
+
+static void Close(vlc_object_t *obj)
+{
+    vout_display_t *vd = (vout_display_t *)obj;
+    vout_display_sys_t *sys = vd->sys;
+
+    /* Restore cursor explicitly (parent window connection will survive) */
+    xcb_change_window_attributes(sys->conn, sys->embed->handle.xid,
+                               XCB_CW_CURSOR, &(uint32_t) { XCB_CURSOR_NONE });
+    xcb_flush(sys->conn);
+
+    vdp_presentation_queue_destroy(sys->vdp, sys->queue);
+    vdp_presentation_queue_target_destroy(sys->vdp, sys->target);
+
+    if (sys->pool != NULL)
+        PoolFree(vd, sys->pool);
+
+    vdp_release_x11(sys->vdp);
+    xcb_disconnect(sys->conn);
+    vout_display_DeleteWindow(vd, sys->embed);
+    free(sys);
+}
-- 
1.8.3.2




More information about the vlc-devel mailing list