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

Frediano Ziglio fziglio at redhat.com
Wed Aug 28 14:44:00 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 ;)
> 

It's not clear why the RFC. From the comment looks like you want them.
Maybe you are not sure about the state of the patches?

> ---
>  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) {

style: bracket on next line

> +     this->x_image = x_image;
> +     this->x_shm = is_shm;
> +     this->new_res = new_res;

Better to use class initialized, even if in this case won't change
much.

> +}
> +
> +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;
> +}
> +

I would inline these in the header. Also I would add const, they don't
allow to change the object.

This "new_resolution" does not apply to the "provides a wrapping class for
XImage", are more related to capture interface. Maybe "XCapturedImg" as name
of the class?

> +/****************** XlibCapture Class Impl *******************/
> +
> +static int (*previous_x_err_handler)(Display *display, XErrorEvent *event) =
> 0;

better "= nullptr" or at least "= NULL" (although 0 for historic reason works).

> +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:

style: too indented.

> +        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);

I suppose you want to reset xshm_image too to avoid dandling pointers.

> +        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

some typos in comment

> +        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);

it looks like this class is a singleton, maybe call XSetErrorHandler
only if previous_x_err_handler is nullptr ?
Are you sure you want this function set for the entire program?

> +    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

Use unique_ptr instead of the comment?

> +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

Frediano


More information about the Spice-devel mailing list