[vlc-devel] [RFC] [PATCH] XCB/RandR: new X11 video output for monitor hardware scaling

Rémi Denis-Courmont remi at remlab.net
Tue Feb 15 22:03:06 CET 2011


This video output provides poor man's hardware video scaling. That
works changing the display port(s) output resolution on-the-fly
using XRandR extension version 1.1. Software chroma conversion is still
required.

This output forces full screen mode at all times, which is its obvious
biggest and fundamental disadvantage over other X11-based outputs.

This patch is incomplete in several ways:

 - There are no provision to restore the original resolution when video
   ends (and possibly also when we loose focus).

 - Most of the code is a shameful cut&pasted from xcb/x11.c. I am
   hesitant on how to deal with this. This code could be factored as a
   submodule of the X11 plugin, but I fear it would reduce readability.

 - Only display fill mode is supported. Crop, zoom and aspect ratio are
   not supported. Unfortunately, vout_display_PlacePicture() does not
   work here: we need to adjust the display size to the video format
   rather than the video format to the display size as we usually do.

 - The plugin must be built manually at the moment as I did not yet
   extend configure.ac:
   # cd modules/video_output; make libxcb_randr_plugin.la
---
 modules/video_output/xcb/randr.c |  566 ++++++++++++++++++++++++++++++++++++++
 1 files changed, 566 insertions(+), 0 deletions(-)
 create mode 100644 modules/video_output/xcb/randr.c

diff --git a/modules/video_output/xcb/randr.c b/modules/video_output/xcb/randr.c
new file mode 100644
index 0000000..d74ccff
--- /dev/null
+++ b/modules/video_output/xcb/randr.c
@@ -0,0 +1,566 @@
+/**
+ * @file randr.c
+ * @brief X RandR video output module for VLC media player
+ */
+/*****************************************************************************
+ * Copyright © 2009-2011 Rémi Denis-Courmont
+ *
+ * This library 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.
+ *
+ * This library 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, 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 <xcb/randr.h>
+#include <xcb/shm.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_vout_display.h>
+#include <vlc_picture_pool.h>
+
+#include "xcb_vlc.h"
+
+static int  Open (vlc_object_t *);
+static void Close (vlc_object_t *);
+
+/*
+ * Module descriptor
+ */
+vlc_module_begin ()
+    set_shortname (N_("X11"))
+    set_description (N_("XRandR video output (XCB)"))
+    set_category (CAT_VIDEO)
+    set_subcategory (SUBCAT_VIDEO_VOUT)
+    set_capability ("vout display", 0)
+    set_callbacks (Open, Close)
+    add_shortcut ("xcb-xrr", "xrr", "xrandr")
+vlc_module_end ()
+
+#define MAX_PICTURES (3)
+
+struct vout_display_sys_t
+{
+    xcb_connection_t *conn;
+    vout_window_t *embed; /* VLC window */
+
+    xcb_cursor_t cursor; /* blank cursor */
+    xcb_window_t window; /* drawable X window */
+    xcb_gcontext_t gc; /* context to put images */
+    bool shm; /* whether to use MIT-SHM */
+    bool visible; /* whether to draw */
+    uint8_t depth; /* useful bits per pixel */
+
+    picture_pool_t *pool; /* picture pool */
+    picture_resource_t resource[MAX_PICTURES];
+};
+
+static picture_pool_t *Pool (vout_display_t *, unsigned);
+static void Display (vout_display_t *, picture_t *, subpicture_t *subpicture);
+static int Control (vout_display_t *, int, va_list);
+static void Manage (vout_display_t *);
+
+static bool CheckRandR (vlc_object_t *obj, xcb_connection_t *conn)
+{
+    xcb_randr_query_version_reply_t *r =
+        xcb_randr_query_version_reply (conn,
+            xcb_randr_query_version (conn, 1, 1), NULL);
+    if (r == NULL)
+        return false;
+
+    msg_Dbg (obj, "using X RandR extension v%"PRIu32".%"PRIu32,
+             r->major_version, r->minor_version);
+    free (r);
+    return true;
+}
+
+static int UpdateResolution (vout_display_t *vd,
+                             unsigned width, unsigned height)
+{
+    vout_display_sys_t *sys = vd->sys;
+    xcb_connection_t *conn = sys->conn;
+    /* Screen infos are requested every time rather than cached.
+     * After all, display ports can change dynamically. */
+    xcb_randr_get_screen_info_reply_t *r =
+        xcb_randr_get_screen_info_reply (conn,
+            xcb_randr_get_screen_info (conn, sys->window), NULL);
+    if (r == NULL)
+    {
+        msg_Err (vd, "cannot get screen sizes");
+        return -1;
+    }
+
+    const xcb_randr_screen_size_t *size = xcb_randr_get_screen_info_sizes (r);
+    uint32_t pixels = 0xFFFFFFFF;
+    uint16_t sizeID;
+    
+    for (int n = xcb_randr_get_screen_info_sizes_length (r), i = 0; i < n; i++)
+    {
+        uint16_t w = size[i].width, h = size[i].height;
+
+        msg_Dbg (vd, "screen size %"PRIu16": %"PRIu16"x%"PRIu16, i, w, h);
+        if (w < width || h < height)
+            continue;
+        if (pixels < (unsigned)(w * h))
+            continue;
+        pixels = w * h;
+        sizeID = i;
+    }
+
+    if (pixels == 0xFFFFFFFF)
+    {
+        free (r);
+        msg_Err (vd, "screen sizes all smaller than %ux%u", width, height);
+        return -1;
+    }
+
+    xcb_randr_set_screen_config_cookie_t ck;
+
+    ck = xcb_randr_set_screen_config (conn, sys->window, XCB_CURRENT_TIME,
+                                 r->config_timestamp, sizeID, r->rotation, 0);
+    free (r);
+    if (xcb_randr_set_screen_config_reply (conn, ck, NULL) == NULL)
+    {
+        msg_Err (vd, "FAIL");
+        return -1;
+    }
+    msg_Dbg (vd, "selected screen size %"PRIu16, sizeID);
+    return 0;
+}
+
+
+static const xcb_depth_t *FindDepth (const xcb_screen_t *scr,
+                                     uint_fast8_t depth)
+{
+    xcb_depth_t *d = NULL;
+    for (xcb_depth_iterator_t it = xcb_screen_allowed_depths_iterator (scr);
+         it.rem > 0 && d == NULL;
+         xcb_depth_next (&it))
+    {
+        if (it.data->depth == depth)
+            d = it.data;
+    }
+
+    return d;
+}
+
+
+/**
+ * Probe the X server.
+ */
+static int Open (vlc_object_t *obj)
+{
+    vout_display_t *vd = (vout_display_t *)obj;
+    vout_display_sys_t *sys = malloc (sizeof (*sys));
+    if (unlikely(sys == NULL))
+        return VLC_ENOMEM;
+
+    vd->sys = sys;
+    sys->pool = NULL;
+
+    /* Get window, connect to X server */
+    xcb_connection_t *conn;
+    const xcb_screen_t *scr;
+    sys->embed = GetWindow (vd, &conn, &scr, &(uint8_t){ 0 });
+    if (sys->embed == NULL)
+    {
+        free (sys);
+        return VLC_EGENERIC;
+    }
+    sys->conn = conn;
+
+    if (!CheckRandR (obj, conn))
+        goto error;
+
+    const xcb_setup_t *setup = xcb_get_setup (conn);
+
+    /* Determine our pixel format */
+    xcb_visualid_t vid;
+    sys->depth = 0;
+
+    for (const xcb_format_t *fmt = xcb_setup_pixmap_formats (setup),
+             *end = fmt + xcb_setup_pixmap_formats_length (setup);
+         fmt < end;
+         fmt++)
+    {
+        if (fmt->depth <= sys->depth)
+            continue; /* no better than earlier format */
+
+        video_format_t fmt_pic = vd->fmt;
+
+        /* Check that the pixmap format is supported by VLC. */
+        switch (fmt->depth)
+        {
+          case 32:
+            if (fmt->bits_per_pixel != 32)
+                continue;
+#ifdef FIXED_VLC_RGBA_MASK
+            fmt_pic.i_chroma = VLC_CODEC_RGBA;
+            break;
+#else
+            msg_Dbg (vd, "X11 visual with alpha-channel not supported");
+            continue;
+#endif
+          case 24:
+            if (fmt->bits_per_pixel == 32)
+                fmt_pic.i_chroma = VLC_CODEC_RGB32;
+            else if (fmt->bits_per_pixel == 24)
+                fmt_pic.i_chroma = VLC_CODEC_RGB24;
+            else
+                continue;
+            break;
+          case 16:
+            if (fmt->bits_per_pixel != 16)
+                continue;
+            fmt_pic.i_chroma = VLC_CODEC_RGB16;
+            break;
+          case 15:
+            if (fmt->bits_per_pixel != 16)
+                continue;
+            fmt_pic.i_chroma = VLC_CODEC_RGB15;
+            break;
+          case 8:
+            if (fmt->bits_per_pixel != 8)
+                continue;
+            fmt_pic.i_chroma = VLC_CODEC_RGB8;
+            break;
+          default:
+            continue;
+        }
+
+        /* Byte sex is a non-issue for 8-bits. It can be worked around with
+         * RGB masks for 24-bits. Too bad for 15-bits and 16-bits. */
+        if (fmt->bits_per_pixel == 16 && setup->image_byte_order != ORDER)
+            continue;
+
+        /* Make sure the X server is sane */
+        assert (fmt->bits_per_pixel > 0);
+        if (unlikely(fmt->scanline_pad % fmt->bits_per_pixel))
+            continue;
+
+        /* Check that the selected screen supports this depth */
+        const xcb_depth_t *d = FindDepth (scr, fmt->depth);
+        if (d == NULL)
+            continue;
+
+        /* Find a visual type for the selected depth */
+        const xcb_visualtype_t *vt = xcb_depth_visuals (d);
+
+        /* First try True Color class */
+        for (int i = xcb_depth_visuals_length (d); i > 0; i--)
+        {
+            if (vt->_class == XCB_VISUAL_CLASS_TRUE_COLOR)
+            {
+                fmt_pic.i_rmask = vt->red_mask;
+                fmt_pic.i_gmask = vt->green_mask;
+                fmt_pic.i_bmask = vt->blue_mask;
+            found_visual:
+                vd->fmt = fmt_pic;
+                vid = vt->visual_id;
+                msg_Dbg (vd, "using X11 visual ID 0x%"PRIx32, vid);
+                sys->depth = fmt->depth;
+                msg_Dbg (vd, " %"PRIu8" bits depth", sys->depth);
+                msg_Dbg (vd, " %"PRIu8" bits per pixel", fmt->bits_per_pixel);
+                msg_Dbg (vd, " %"PRIu8" bits line pad", fmt->scanline_pad);
+                goto found_format;
+            }
+            vt++;
+        }
+
+        /* Then try Static Gray class */
+        if (fmt->depth != 8)
+            continue;
+        vt = xcb_depth_visuals (d);
+        for (int i = xcb_depth_visuals_length (d); i > 0 && !vid; i--)
+        {
+            if (vt->_class == XCB_VISUAL_CLASS_STATIC_GRAY)
+            {
+                fmt_pic.i_chroma = VLC_CODEC_GREY;
+                goto found_visual;
+            }
+            vt++;
+        }
+    }
+
+    msg_Err (obj, "no supported pixel format & visual");
+    goto error;
+
+found_format:;
+    /* Create colormap (needed to select non-default visual) */
+    xcb_colormap_t cmap;
+    if (vid != scr->root_visual)
+    {
+        cmap = xcb_generate_id (conn);
+        xcb_create_colormap (conn, XCB_COLORMAP_ALLOC_NONE,
+                             cmap, scr->root, vid);
+    }
+    else
+        cmap = scr->default_colormap;
+
+    /* Create window */
+    /* FIXME */
+    unsigned width = vd->fmt.i_visible_width;
+    unsigned height = vd->fmt.i_visible_height;
+
+    sys->window = xcb_generate_id (conn);
+    sys->gc = xcb_generate_id (conn);
+    xcb_pixmap_t pixmap = xcb_generate_id (conn);
+    {
+        const uint32_t mask =
+            XCB_CW_BACK_PIXMAP |
+            XCB_CW_BACK_PIXEL |
+            XCB_CW_BORDER_PIXMAP |
+            XCB_CW_BORDER_PIXEL |
+            XCB_CW_COLORMAP;
+        const uint32_t values[] = {
+            /* XCB_CW_BACK_PIXMAP */
+            pixmap,
+            /* XCB_CW_BACK_PIXEL */
+            scr->black_pixel,
+            /* XCB_CW_BORDER_PIXMAP */
+            pixmap,
+            /* XCB_CW_BORDER_PIXEL */
+            scr->black_pixel,
+            /* XCB_CW_COLORMAP */
+            cmap,
+        };
+        xcb_void_cookie_t c;
+
+        xcb_create_pixmap (conn, sys->depth, pixmap, scr->root, 1, 1);
+        c = xcb_create_window_checked (conn, sys->depth, sys->window,
+                                       sys->embed->handle.xid, 0, 0,
+                                       width, height, 0,
+                                       XCB_WINDOW_CLASS_INPUT_OUTPUT,
+                                       vid, mask, values);
+        xcb_map_window (conn, sys->window);
+        /* Create graphic context (I wonder why the heck do we need this) */
+        xcb_create_gc (conn, sys->gc, sys->window, 0, NULL);
+
+        if (CheckError (vd, conn, "cannot create X11 window", c))
+            goto error;
+    }
+    msg_Dbg (vd, "using X11 window %08"PRIx32, sys->window);
+    msg_Dbg (vd, "using X11 graphic context %08"PRIx32, sys->gc);
+
+    sys->cursor = CreateBlankCursor (conn, scr);
+    sys->visible = false;
+    sys->shm = CheckSHM (obj, conn);
+
+
+    /* Setup vout_display_t once everything is fine */
+    vd->info.has_pictures_invalid = false;
+    vd->info.has_event_thread = true;
+
+    vd->pool = Pool;
+    vd->prepare = NULL;
+    vd->display = Display;
+    vd->control = Control;
+    vd->manage = Manage;
+
+    /* This output only works in full screen mode */
+    if (vout_window_SetFullScreen (sys->embed, true)
+     || UpdateResolution (vd, width, height))
+        goto error;
+
+    vout_display_SendEventFullscreen (vd, true);
+    vout_display_SendEventDisplaySize (vd, width, height, true);
+
+    return VLC_SUCCESS;
+
+error:
+    Close (obj);
+    return VLC_EGENERIC;
+}
+
+
+/**
+ * Disconnect from the X server.
+ */
+static void Close (vlc_object_t *obj)
+{
+    vout_display_t *vd = (vout_display_t *)obj;
+    vout_display_sys_t *sys = vd->sys;
+
+    if (sys->pool)
+    {
+        for (unsigned i = 0; i < MAX_PICTURES; i++)
+        {
+            picture_resource_t *res = sys->resource + i;
+
+            if (!res->p->p_pixels)
+                break;
+            PictureResourceFree (res, NULL);
+        }
+        picture_pool_Delete (sys->pool);
+    }
+
+    /* show the default cursor */
+    xcb_change_window_attributes (sys->conn, sys->embed->handle.xid, XCB_CW_CURSOR,
+                                  &(uint32_t) { XCB_CURSOR_NONE });
+    xcb_flush (sys->conn);
+
+    /* colormap, window and context are garbage-collected by X */
+    xcb_disconnect (sys->conn);
+    vout_display_DeleteWindow (vd, sys->embed);
+    free (sys);
+}
+
+/**
+ * Return a direct buffer
+ */
+static picture_pool_t *Pool (vout_display_t *vd, unsigned requested_count)
+{
+    vout_display_sys_t *sys = vd->sys;
+    (void)requested_count;
+
+    if (sys->pool)
+        return sys->pool;
+
+    picture_t *pic = picture_NewFromFormat (&vd->fmt);
+    if (!pic)
+        return NULL;
+
+    assert (pic->i_planes == 1);
+    memset (sys->resource, 0, sizeof(sys->resource));
+
+    unsigned count;
+    picture_t *pic_array[MAX_PICTURES];
+    for (count = 0; count < MAX_PICTURES; count++)
+    {
+        picture_resource_t *res = &sys->resource[count];
+
+        res->p->i_lines = pic->p->i_lines;
+        res->p->i_pitch = pic->p->i_pitch;
+        if (PictureResourceAlloc (vd, res, res->p->i_pitch * res->p->i_lines,
+                                  sys->conn, sys->shm))
+            break;
+        pic_array[count] = picture_NewFromResource (&vd->fmt, res);
+        if (!pic_array[count])
+        {
+            PictureResourceFree (res, sys->conn);
+            memset (res, 0, sizeof(*res));
+            break;
+        }
+    }
+    picture_Release (pic);
+
+    if (count == 0)
+        return NULL;
+
+    sys->pool = picture_pool_New (count, pic_array);
+    /* TODO release picture resources if NULL */
+    xcb_flush (sys->conn);
+    return sys->pool;
+}
+
+/**
+ * Sends an image to the X server.
+ */
+static void Display (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
+{
+    vout_display_sys_t *sys = vd->sys;
+    xcb_shm_seg_t segment = pic->p_sys->segment;
+    xcb_void_cookie_t ck;
+
+    if (!sys->visible)
+        goto out;
+    if (segment != 0)
+        ck = xcb_shm_put_image_checked (sys->conn, sys->window, sys->gc,
+          /* real width */ pic->p->i_pitch / pic->p->i_pixel_pitch,
+         /* real height */ pic->p->i_lines,
+                   /* x */ vd->fmt.i_x_offset,
+                   /* y */ vd->fmt.i_y_offset,
+               /* width */ vd->fmt.i_visible_width,
+              /* height */ vd->fmt.i_visible_height,
+                           0, 0, sys->depth, XCB_IMAGE_FORMAT_Z_PIXMAP,
+                           0, segment, 0);
+    else
+    {
+        const size_t offset = vd->fmt.i_y_offset * pic->p->i_pitch;
+        const unsigned lines = pic->p->i_lines - vd->fmt.i_y_offset;
+
+        ck = xcb_put_image_checked (sys->conn, XCB_IMAGE_FORMAT_Z_PIXMAP,
+                       sys->window, sys->gc,
+                       pic->p->i_pitch / pic->p->i_pixel_pitch,
+                       lines, -vd->fmt.i_x_offset, 0, 0, sys->depth,
+                       pic->p->i_pitch * lines, pic->p->p_pixels + offset);
+    }
+
+    /* Wait for reply. This makes sure that the X server gets CPU time to
+     * display the picture. xcb_flush() is *not* sufficient: especially with
+     * shared memory the PUT requests are so short that many of them can fit in
+     * X11 socket output buffer before the kernel preempts VLC. */
+    xcb_generic_error_t *e = xcb_request_check (sys->conn, ck);
+    if (e != NULL)
+    {
+        msg_Dbg (vd, "%s: X11 error %d", "cannot put image", e->error_code);
+        free (e);
+    }
+
+    /* FIXME might be WAY better to wait in some case (be carefull with
+     * VOUT_DISPLAY_RESET_PICTURES if done) + does not work with
+     * vout_display wrapper. */
+out:
+    picture_Release (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_CHANGE_FULLSCREEN:
+        return VLC_EGENERIC;
+
+    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:
+    case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
+    case VOUT_DISPLAY_CHANGE_ZOOM:
+    case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
+    case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
+        /* TODO: change resolution */
+        return VLC_EGENERIC;
+
+    /* Hide the mouse. It will be send when
+     * vout_display_t::info.b_hide_mouse is false */
+    case VOUT_DISPLAY_HIDE_MOUSE:
+        xcb_change_window_attributes (sys->conn, sys->embed->handle.xid,
+                                  XCB_CW_CURSOR, &(uint32_t){ sys->cursor });
+        return VLC_SUCCESS;
+
+    default:
+        msg_Err (vd, "Unknown request in XCB vout display");
+        return VLC_EGENERIC;
+    }
+}
+
+static void Manage (vout_display_t *vd)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    ManageEvent (vd, sys->conn, &sys->visible);
+}
-- 
1.7.2.3




More information about the vlc-devel mailing list