[Spice-devel] [RFC spice-streaming-agent 1/3] Add Xlib capture helper files

Snir Sheriber ssheribe at redhat.com
Mon Aug 26 08:39:46 UTC 2019


The Xlib capture helper provides a wrapping class for XImage, Display
information and other capturing related functionalities which allows
to use unified and simple API for screen capturing using X.
This also utilize the X MIT-SHM extension which improves capturing
speed, hence, xext is required.
---

The original idea was just to use the X ancient MIT-SHM extension which
reduce CPU utilization significantly when using Xlib capturing.
Since this capturing method is currently used by two different plugins
it was suggested to use one common class for that, this class is added
by this patch and the 2 following patches makes the existing plugins
to use this class.

If you have any other suggestions or comments please do ;)

---
 configure.ac         |   1 +
 src/xlib-capture.cpp | 169 +++++++++++++++++++++++++++++++++++++++++++
 src/xlib-capture.hpp |  53 ++++++++++++++
 3 files changed, 223 insertions(+)
 create mode 100644 src/xlib-capture.cpp
 create mode 100644 src/xlib-capture.hpp

diff --git a/configure.ac b/configure.ac
index 1c7788b..3220e27 100644
--- a/configure.ac
+++ b/configure.ac
@@ -38,6 +38,7 @@ PKG_CHECK_MODULES(DRM, libdrm)
 PKG_CHECK_MODULES(X11, x11)
 PKG_CHECK_MODULES(XFIXES, xfixes)
 PKG_CHECK_MODULES(XRANDR, xrandr)
+PKG_CHECK_MODULES(XEXT, xext)
 
 PKG_CHECK_MODULES(JPEG, libjpeg, , [
     AC_CHECK_LIB(jpeg, jpeg_destroy_decompress,
diff --git a/src/xlib-capture.cpp b/src/xlib-capture.cpp
new file mode 100644
index 0000000..7837ee2
--- /dev/null
+++ b/src/xlib-capture.cpp
@@ -0,0 +1,169 @@
+/* A helper classes that wraps X display information XImage capturing functions
+ *
+ * \copyright
+ * Copyright 2019 Red Hat Inc. All rights reserved.
+ */
+
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include "xlib-capture.hpp"
+
+namespace spice {
+namespace streaming_agent {
+
+/******************** XImg Class Impl ***********************/
+
+XImg::XImg(XImage *x_image, bool is_shm, bool new_res) {
+     this->x_image = x_image;
+     this->x_shm = is_shm;
+     this->new_res = new_res;
+}
+
+XImg::~XImg() {
+    if (!x_shm) {
+        XDestroyImage(x_image);
+    } // else the XlibCapture will destroy it
+}
+
+char *XImg::get_data() {
+    return x_image->data;
+}
+
+int XImg::height() {
+    return x_image->height;
+}
+
+int XImg::width() {
+    return x_image->width;
+}
+
+int XImg::data_size() {
+    return x_image->height * x_image->bytes_per_line;
+}
+
+bool XImg::new_resolution() {
+   return new_res;
+}
+
+/****************** XlibCapture Class Impl *******************/
+
+static int (*previous_x_err_handler)(Display *display, XErrorEvent *event) = 0;
+static bool xerror = false;
+
+/* This is used mainly to silence x error in case of resolution change */
+static int x_err_handler(Display *display, XErrorEvent *event) {
+    //TODO: print some error information ?
+    switch (event->error_code) {
+        case BadAccess:
+        case BadMatch:
+            xerror = true;
+            return 0;
+        default:
+            return previous_x_err_handler(display, event);
+    }
+}
+
+void XlibCapture::init_xshm() {
+    Visual *visual = DefaultVisual(display, screen);
+    unsigned int depth = DefaultDepth(display, screen);
+    XWindowAttributes win_info;
+
+    xshm_enabled = false;
+    xerror = false;
+    // Create Shared Memory XImage
+    XGetWindowAttributes(display, win, &win_info); // update attributes
+    xshm_image = XShmCreateImage(display, visual, depth, ZPixmap, nullptr,
+                                 &shm_info, win_info.width, win_info.height);
+    if (!xshm_image) {
+        return;
+    }
+    // Allocate shm buffer
+    int shm_id = shmget(IPC_PRIVATE, xshm_image->bytes_per_line * xshm_image->height, IPC_CREAT|0777);
+    if (shm_id == -1) {
+        XDestroyImage(xshm_image);
+        return;
+    }
+
+    // Attach share memory bufer
+    void *shm_addr = shmat(shm_id, nullptr, 0);
+    if (shm_addr == (void*)-1) {
+        XDestroyImage(xshm_image);
+        shmctl(shm_id, IPC_RMID, nullptr);
+        return;
+    }
+    shm_info.shmid = shm_id;
+    shm_info.shmaddr = xshm_image->data = (char*)shm_addr;
+    shm_info.readOnly = false;
+    XShmAttach(display, &shm_info);
+    XSync(display, false);
+
+    if (!xerror) {
+        xshm_enabled = true;
+        return; // XShm initialization succeeded
+    }
+}
+
+XImg *XlibCapture::capture() {
+    XWindowAttributes win_info;
+    bool changed = false;
+    int tries = 0;
+
+    /* This code flow allows to overcome resolution change that occurs just
+     * after getting the attributes and before asking for the XImage
+     */
+    do {
+        XGetWindowAttributes(display, win, &win_info); // update attributes
+
+        if (last_width != win_info.width || last_height != win_info.height) {
+            if (xshm_enabled && xshm_image) { // reinit xshm extention
+                deinit_xshm();
+                init_xshm();
+            }
+            changed = true; // resolution changed
+            last_width = win_info.width;
+            last_height = win_info.height;
+        }
+        // TODO: round down the resoultion / use custom resoultion
+        if (xshm_enabled && xshm_image) {
+            if (XShmGetImage(display, win, xshm_image, win_info.x, win_info.y, AllPlanes)) {
+                return new XImg(xshm_image, true, changed);
+            }
+        }
+        XImage *image = XGetImage(display, win, win_info.x, win_info.y, win_info.width,
+                                  win_info.height, AllPlanes, ZPixmap);
+        if (image) {
+            return new XImg(image, false, changed);
+        }
+    } while (++tries < 2); // there was an error to get the image, try once more (might be a resoultion change)
+    return nullptr; // fail
+}
+
+void XlibCapture::deinit_xshm() {
+    if (xshm_enabled && xshm_image) {
+        XShmDetach (display, &shm_info);
+        xshm_image->f.destroy_image(xshm_image);
+        shmdt(shm_info.shmaddr);
+        shmctl(shm_info.shmid, IPC_RMID, 0);
+        xshm_image = nullptr;
+        xshm_enabled = false;
+    }
+}
+
+XlibCapture::XlibCapture(Display *display):
+    display(display)
+{
+    XWindowAttributes win_info;
+    this->screen = XDefaultScreen(display);
+    this->win = RootWindow(display, screen);
+    XGetWindowAttributes(display, win, &win_info); // update attributes
+    last_width = -1;
+    last_height = -1;
+    previous_x_err_handler = XSetErrorHandler(x_err_handler);
+    init_xshm();
+}
+
+XlibCapture::~XlibCapture() {
+    deinit_xshm();
+}
+
+}} // namespace spice::streaming_agent
diff --git a/src/xlib-capture.hpp b/src/xlib-capture.hpp
new file mode 100644
index 0000000..93200dc
--- /dev/null
+++ b/src/xlib-capture.hpp
@@ -0,0 +1,53 @@
+/*  A helper classes that wraps X display information XImage capturing functions
+ *
+ * \copyright
+ * Copyright 2019 Red Hat Inc. All rights reserved.
+ */
+
+#ifndef SPICE_STREAMING_AGENT_XLIB_CAPTURE_HPP // need header file?
+#define SPICE_STREAMING_AGENT_XLIB_CAPTURE_HPP
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/XShm.h>
+
+namespace spice {
+namespace streaming_agent {
+
+class XImg
+{
+public:
+    XImg(XImage *x_iamge, bool is_shm, bool new_resolution);
+    ~XImg();
+    char *get_data();
+    int data_size(); // with considering stride
+    int height();
+    int width();
+    bool new_resolution();
+private:
+    XImage *x_image;
+    bool x_shm;
+    bool new_res;
+};
+
+class XlibCapture
+{
+public:
+    XlibCapture(Display *display); // add empty constructor?
+    ~XlibCapture();
+    XImg *capture(); // caller should delete the return image
+private:
+    int last_width, last_height;
+    Display *const display;
+    int screen;
+    Window win;
+    XImage *xshm_image = nullptr;
+    XShmSegmentInfo shm_info;
+    bool xshm_enabled = false;
+    void init_xshm();
+    void deinit_xshm();
+};
+
+}} // namespace spice::streaming_agent
+
+#endif // SPICE_STREAMING_AGENT_XLIB_CAPTURE_HPP
-- 
2.21.0



More information about the Spice-devel mailing list