x11grab.c 17.8 KB
Newer Older
1 2
/*
 * X11 video grab interface
3
 *
4
 * This file is part of Libav.
5
 *
6
 * Libav integration:
7 8
 * Copyright (C) 2006 Clemens Fruhwirth <clemens@endorphin.org>
 *                    Edouard Gomez <ed.gomez@free.fr>
9
 *
10
 * This file contains code from grab.c:
11
 * Copyright (c) 2000-2001 Fabrice Bellard
12 13
 *
 * This file contains code from the xvidcap project:
14 15
 * Copyright (C) 1997-1998 Rasca, Berlin
 *               2003-2004 Karl H. Beckers, Frankfurt
16
 *
17
 * Libav is free software; you can redistribute it and/or modify
18 19 20 21
 * 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.
 *
22
 * Libav is distributed in the hope that it will be useful,
23 24 25 26
 * 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.
 *
27
 * You should have received a copy of the GNU General Public License
28
 * along with Libav; if not, write to the Free Software
29
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
30
 */
31

Edouard Gomez's avatar
Edouard Gomez committed
32
/**
33
 * @file
Edouard Gomez's avatar
Edouard Gomez committed
34 35 36 37
 * X11 frame device demuxer by Clemens Fruhwirth <clemens@endorphin.org>
 * and Edouard Gomez <ed.gomez@free.fr>.
 */

38
#include "config.h"
39
#include "libavformat/avformat.h"
40 41 42
#include "libavutil/log.h"
#include "libavutil/opt.h"
#include "libavutil/parseutils.h"
43 44 45 46 47
#include <time.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xlibint.h>
#include <X11/Xproto.h>
48
#include <X11/Xutil.h>
49 50
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
Roxis's avatar
Roxis committed
51
#include <X11/extensions/Xfixes.h>
52

Edouard Gomez's avatar
Edouard Gomez committed
53 54 55
/**
 * X11 Device Demuxer context
 */
56
struct x11_grab
57
{
58
    const AVClass *class;    /**< Class for private options. */
Edouard Gomez's avatar
Edouard Gomez committed
59 60 61 62
    int frame_size;          /**< Size in bytes of a grabbed frame */
    AVRational time_base;    /**< Time base */
    int64_t time_frame;      /**< Current time */

63
    char *video_size;        /**< String describing video size, set by a private option. */
Edouard Gomez's avatar
Edouard Gomez committed
64 65 66 67 68 69 70 71 72
    int height;              /**< Height of the grab frame */
    int width;               /**< Width of the grab frame */
    int x_off;               /**< Horizontal top-left corner coordinate */
    int y_off;               /**< Vertical top-left corner coordinate */

    Display *dpy;            /**< X11 display from which x11grab grabs frames */
    XImage *image;           /**< X11 image holding the grab */
    int use_shm;             /**< !0 when using XShm extension */
    XShmSegmentInfo shminfo; /**< When using XShm, keeps track of XShm infos */
73
    int  draw_mouse;         /**< Set by a private option. */
74
    int  follow_mouse;       /**< Set by a private option. */
75
    char *framerate;         /**< Set by a private option. */
76
};
Edouard Gomez's avatar
Edouard Gomez committed
77 78

/**
79
 * Initialize the x11 grab device demuxer (public device demuxer API).
Edouard Gomez's avatar
Edouard Gomez committed
80 81 82 83
 *
 * @param s1 Context from avformat core
 * @param ap Parameters from avformat core
 * @return <ul>
84
 *          <li>AVERROR(ENOMEM) no memory left</li>
85
 *          <li>AVERROR(EIO) other failure case</li>
Edouard Gomez's avatar
Edouard Gomez committed
86 87 88
 *          <li>0 success</li>
 *         </ul>
 */
89 90
static int
x11grab_read_header(AVFormatContext *s1, AVFormatParameters *ap)
91
{
92
    struct x11_grab *x11grab = s1->priv_data;
93 94
    Display *dpy;
    AVStream *st = NULL;
95
    enum PixelFormat input_pixfmt;
96
    XImage *image;
Edouard Gomez's avatar
Edouard Gomez committed
97 98
    int x_off = 0;
    int y_off = 0;
99
    int screen;
100
    int use_shm;
101
    char *param, *offset;
102
    int ret = 0;
103
    AVRational framerate;
104

105
    param = av_strdup(s1->filename);
106 107 108
    offset = strchr(param, '+');
    if (offset) {
        sscanf(offset, "%d,%d", &x_off, &y_off);
109
        x11grab->draw_mouse = !strstr(offset, "nomouse");
110 111 112
        *offset= 0;
    }

113 114 115 116
    if ((ret = av_parse_video_size(&x11grab->width, &x11grab->height, x11grab->video_size)) < 0) {
        av_log(s1, AV_LOG_ERROR, "Couldn't parse video size.\n");
        goto out;
    }
117 118 119 120
    if ((ret = av_parse_video_rate(&framerate, x11grab->framerate)) < 0) {
        av_log(s1, AV_LOG_ERROR, "Could not parse framerate: %s.\n", x11grab->framerate);
        goto out;
    }
121 122 123 124 125
#if FF_API_FORMAT_PARAMETERS
    if (ap->width > 0)
        x11grab->width = ap->width;
    if (ap->height > 0)
        x11grab->height = ap->height;
126 127
    if (ap->time_base.num)
        framerate = (AVRational){ap->time_base.den, ap->time_base.num};
128 129 130
#endif
    av_log(s1, AV_LOG_INFO, "device: %s -> display: %s x: %d y: %d width: %d height: %d\n",
           s1->filename, param, x_off, y_off, x11grab->width, x11grab->height);
131 132 133 134

    dpy = XOpenDisplay(param);
    if(!dpy) {
        av_log(s1, AV_LOG_ERROR, "Could not open X display.\n");
135 136
        ret = AVERROR(EIO);
        goto out;
137
    }
138 139 140

    st = av_new_stream(s1, 0);
    if (!st) {
141 142
        ret = AVERROR(ENOMEM);
        goto out;
143 144 145
    }
    av_set_pts_info(st, 64, 1, 1000000); /* 64 bits pts in us */

146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
    screen = DefaultScreen(dpy);

    if (x11grab->follow_mouse) {
        int screen_w, screen_h;
        Window w;

        screen_w = DisplayWidth(dpy, screen);
        screen_h = DisplayHeight(dpy, screen);
        XQueryPointer(dpy, RootWindow(dpy, screen), &w, &w, &x_off, &y_off, &ret, &ret, &ret);
        x_off -= x11grab->width / 2;
        y_off -= x11grab->height / 2;
        x_off = FFMIN(FFMAX(x_off, 0), screen_w - x11grab->width);
        y_off = FFMIN(FFMAX(y_off, 0), screen_h - x11grab->height);
        av_log(s1, AV_LOG_INFO, "followmouse is enabled, resetting grabbing region to x: %d y: %d\n", x_off, y_off);
    }

162
    use_shm = XShmQueryExtension(dpy);
Edouard Gomez's avatar
Edouard Gomez committed
163
    av_log(s1, AV_LOG_INFO, "shared memory extension %s found\n", use_shm ? "" : "not");
164 165 166 167 168 169 170 171 172

    if(use_shm) {
        int scr = XDefaultScreen(dpy);
        image = XShmCreateImage(dpy,
                                DefaultVisual(dpy, scr),
                                DefaultDepth(dpy, scr),
                                ZPixmap,
                                NULL,
                                &x11grab->shminfo,
173
                                x11grab->width, x11grab->height);
174 175 176 177 178
        x11grab->shminfo.shmid = shmget(IPC_PRIVATE,
                                        image->bytes_per_line * image->height,
                                        IPC_CREAT|0777);
        if (x11grab->shminfo.shmid == -1) {
            av_log(s1, AV_LOG_ERROR, "Fatal: Can't get shared memory!\n");
179 180
            ret = AVERROR(ENOMEM);
            goto out;
181 182 183 184 185 186 187
        }
        x11grab->shminfo.shmaddr = image->data = shmat(x11grab->shminfo.shmid, 0, 0);
        x11grab->shminfo.readOnly = False;

        if (!XShmAttach(dpy, &x11grab->shminfo)) {
            av_log(s1, AV_LOG_ERROR, "Fatal: Failed to attach shared memory!\n");
            /* needs some better error subroutine :) */
188 189
            ret = AVERROR(EIO);
            goto out;
190 191
        }
    } else {
192
        image = XGetImage(dpy, RootWindow(dpy, screen),
193
                          x_off,y_off,
194
                          x11grab->width, x11grab->height,
195 196 197 198 199
                          AllPlanes, ZPixmap);
    }

    switch (image->bits_per_pixel) {
    case 8:
Diego Biurrun's avatar
Diego Biurrun committed
200
        av_log (s1, AV_LOG_DEBUG, "8 bit palette\n");
201 202 203
        input_pixfmt = PIX_FMT_PAL8;
        break;
    case 16:
Edouard Gomez's avatar
Edouard Gomez committed
204 205 206
        if (       image->red_mask   == 0xf800 &&
                   image->green_mask == 0x07e0 &&
                   image->blue_mask  == 0x001f ) {
207 208
            av_log (s1, AV_LOG_DEBUG, "16 bit RGB565\n");
            input_pixfmt = PIX_FMT_RGB565;
Edouard Gomez's avatar
Edouard Gomez committed
209 210 211
        } else if (image->red_mask   == 0x7c00 &&
                   image->green_mask == 0x03e0 &&
                   image->blue_mask  == 0x001f ) {
212 213 214 215 216
            av_log(s1, AV_LOG_DEBUG, "16 bit RGB555\n");
            input_pixfmt = PIX_FMT_RGB555;
        } else {
            av_log(s1, AV_LOG_ERROR, "RGB ordering at image depth %i not supported ... aborting\n", image->bits_per_pixel);
            av_log(s1, AV_LOG_ERROR, "color masks: r 0x%.6lx g 0x%.6lx b 0x%.6lx\n", image->red_mask, image->green_mask, image->blue_mask);
217 218
            ret = AVERROR(EIO);
            goto out;
219 220 221
        }
        break;
    case 24:
Edouard Gomez's avatar
Edouard Gomez committed
222 223 224
        if (        image->red_mask   == 0xff0000 &&
                    image->green_mask == 0x00ff00 &&
                    image->blue_mask  == 0x0000ff ) {
225
            input_pixfmt = PIX_FMT_BGR24;
Edouard Gomez's avatar
Edouard Gomez committed
226 227 228
        } else if ( image->red_mask   == 0x0000ff &&
                    image->green_mask == 0x00ff00 &&
                    image->blue_mask  == 0xff0000 ) {
229 230 231 232
            input_pixfmt = PIX_FMT_RGB24;
        } else {
            av_log(s1, AV_LOG_ERROR,"rgb ordering at image depth %i not supported ... aborting\n", image->bits_per_pixel);
            av_log(s1, AV_LOG_ERROR, "color masks: r 0x%.6lx g 0x%.6lx b 0x%.6lx\n", image->red_mask, image->green_mask, image->blue_mask);
233 234
            ret = AVERROR(EIO);
            goto out;
235 236 237
        }
        break;
    case 32:
238
        input_pixfmt = PIX_FMT_RGB32;
239 240 241
        break;
    default:
        av_log(s1, AV_LOG_ERROR, "image depth %i not supported ... aborting\n", image->bits_per_pixel);
242 243
        ret = AVERROR(EINVAL);
        goto out;
244 245
    }

246
    x11grab->frame_size = x11grab->width * x11grab->height * image->bits_per_pixel/8;
247
    x11grab->dpy = dpy;
248 249
    x11grab->time_base  = (AVRational){framerate.den, framerate.num};
    x11grab->time_frame = av_gettime() / av_q2d(x11grab->time_base);
250 251 252 253 254
    x11grab->x_off = x_off;
    x11grab->y_off = y_off;
    x11grab->image = image;
    x11grab->use_shm = use_shm;

255
    st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
256
    st->codec->codec_id = CODEC_ID_RAWVIDEO;
257 258
    st->codec->width  = x11grab->width;
    st->codec->height = x11grab->height;
259
    st->codec->pix_fmt = input_pixfmt;
260 261
    st->codec->time_base = x11grab->time_base;
    st->codec->bit_rate = x11grab->frame_size * 1/av_q2d(x11grab->time_base) * 8;
262

263 264
out:
    return ret;
265 266
}

Edouard Gomez's avatar
Edouard Gomez committed
267
/**
268
 * Paint a mouse pointer in an X11 image.
Edouard Gomez's avatar
Edouard Gomez committed
269
 *
Diego Biurrun's avatar
Diego Biurrun committed
270
 * @param image image to paint the mouse pointer to
Edouard Gomez's avatar
Edouard Gomez committed
271 272 273
 * @param s context used to retrieve original grabbing rectangle
 *          coordinates
 */
274
static void
Roxis's avatar
Roxis committed
275
paint_mouse_pointer(XImage *image, struct x11_grab *s)
276
{
277 278 279 280
    int x_off = s->x_off;
    int y_off = s->y_off;
    int width = s->width;
    int height = s->height;
Roxis's avatar
Roxis committed
281 282 283 284 285
    Display *dpy = s->dpy;
    XFixesCursorImage *xcim;
    int x, y;
    int line, column;
    int to_line, to_column;
286 287 288 289 290 291 292 293 294 295
    int pixstride = image->bits_per_pixel >> 3;
    /* Warning: in its insanity, xlib provides unsigned image data through a
     * char* pointer, so we have to make it uint8_t to make things not break.
     * Anyone who performs further investigation of the xlib API likely risks
     * permanent brain damage. */
    uint8_t *pix = image->data;

    /* Code doesn't currently support 16-bit or PAL8 */
    if (image->bits_per_pixel != 24 && image->bits_per_pixel != 32)
        return;
296

Carl Eugen Hoyos's avatar
Carl Eugen Hoyos committed
297
    xcim = XFixesGetCursorImage(dpy);
298

Roxis's avatar
Roxis committed
299 300 301 302 303 304 305 306
    x = xcim->x - xcim->xhot;
    y = xcim->y - xcim->yhot;

    to_line = FFMIN((y + xcim->height), (height + y_off));
    to_column = FFMIN((x + xcim->width), (width + x_off));

    for (line = FFMAX(y, y_off); line < to_line; line++) {
        for (column = FFMAX(x, x_off); column < to_column; column++) {
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
            int  xcim_addr = (line - y) * xcim->width + column - x;
            int image_addr = ((line - y_off) * width + column - x_off) * pixstride;
            int r = (uint8_t)(xcim->pixels[xcim_addr] >>  0);
            int g = (uint8_t)(xcim->pixels[xcim_addr] >>  8);
            int b = (uint8_t)(xcim->pixels[xcim_addr] >> 16);
            int a = (uint8_t)(xcim->pixels[xcim_addr] >> 24);

            if (a == 255) {
                pix[image_addr+0] = r;
                pix[image_addr+1] = g;
                pix[image_addr+2] = b;
            } else if (a) {
                /* pixel values from XFixesGetCursorImage come premultiplied by alpha */
                pix[image_addr+0] = r + (pix[image_addr+0]*(255-a) + 255/2) / 255;
                pix[image_addr+1] = g + (pix[image_addr+1]*(255-a) + 255/2) / 255;
                pix[image_addr+2] = b + (pix[image_addr+2]*(255-a) + 255/2) / 255;
323
            }
324 325
        }
    }
Roxis's avatar
Roxis committed
326 327 328

    XFree(xcim);
    xcim = NULL;
329 330 331
}


Edouard Gomez's avatar
Edouard Gomez committed
332
/**
333
 * Read new data in the image structure.
Edouard Gomez's avatar
Edouard Gomez committed
334 335
 *
 * @param dpy X11 display to grab from
336
 * @param d
Edouard Gomez's avatar
Edouard Gomez committed
337 338 339 340
 * @param image Image where the grab will be put
 * @param x Top-Left grabbing rectangle horizontal coordinate
 * @param y Top-Left grabbing rectangle vertical coordinate
 * @return 0 if error, !0 if successful
341 342
 */
static int
Edouard Gomez's avatar
Edouard Gomez committed
343
xget_zpixmap(Display *dpy, Drawable d, XImage *image, int x, int y)
344
{
345 346 347 348 349
    xGetImageReply rep;
    xGetImageReq *req;
    long nbytes;

    if (!image) {
Edouard Gomez's avatar
Edouard Gomez committed
350
        return 0;
351 352 353 354 355 356 357 358 359 360 361 362 363 364
    }

    LockDisplay(dpy);
    GetReq(GetImage, req);

    /* First set up the standard stuff in the request */
    req->drawable = d;
    req->x = x;
    req->y = y;
    req->width = image->width;
    req->height = image->height;
    req->planeMask = (unsigned int)AllPlanes;
    req->format = ZPixmap;

Edouard Gomez's avatar
Edouard Gomez committed
365
    if (!_XReply(dpy, (xReply *)&rep, 0, xFalse) || !rep.length) {
366 367
        UnlockDisplay(dpy);
        SyncHandle();
Edouard Gomez's avatar
Edouard Gomez committed
368
        return 0;
369 370 371 372 373 374 375
    }

    nbytes = (long)rep.length << 2;
    _XReadPad(dpy, image->data, nbytes);

    UnlockDisplay(dpy);
    SyncHandle();
Edouard Gomez's avatar
Edouard Gomez committed
376
    return 1;
377 378
}

Edouard Gomez's avatar
Edouard Gomez committed
379
/**
380
 * Grab a frame from x11 (public device demuxer API).
Edouard Gomez's avatar
Edouard Gomez committed
381 382 383 384 385
 *
 * @param s1 Context from avformat core
 * @param pkt Packet holding the brabbed frame
 * @return frame size in bytes
 */
386 387
static int
x11grab_read_packet(AVFormatContext *s1, AVPacket *pkt)
388
{
389
    struct x11_grab *s = s1->priv_data;
390 391 392 393 394
    Display *dpy = s->dpy;
    XImage *image = s->image;
    int x_off = s->x_off;
    int y_off = s->y_off;

395 396 397 398
    int screen;
    Window root;
    int follow_mouse = s->follow_mouse;

399 400 401 402
    int64_t curtime, delay;
    struct timespec ts;

    /* Calculate the time of the next frame */
403
    s->time_frame += INT64_C(1000000);
404 405 406 407

    /* wait based on the frame rate */
    for(;;) {
        curtime = av_gettime();
Edouard Gomez's avatar
Edouard Gomez committed
408
        delay = s->time_frame * av_q2d(s->time_base) - curtime;
409
        if (delay <= 0) {
410 411
            if (delay < INT64_C(-1000000) * av_q2d(s->time_base)) {
                s->time_frame += INT64_C(1000000);
412 413 414 415 416 417 418 419
            }
            break;
        }
        ts.tv_sec = delay / 1000000;
        ts.tv_nsec = (delay % 1000000) * 1000;
        nanosleep(&ts, NULL);
    }

420 421 422
    av_init_packet(pkt);
    pkt->data = image->data;
    pkt->size = s->frame_size;
Edouard Gomez's avatar
Edouard Gomez committed
423
    pkt->pts = curtime;
424

425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
    screen = DefaultScreen(dpy);
    root = RootWindow(dpy, screen);
    if (follow_mouse) {
        int screen_w, screen_h;
        int pointer_x, pointer_y, _;
        Window w;

        screen_w = DisplayWidth(dpy, screen);
        screen_h = DisplayHeight(dpy, screen);
        XQueryPointer(dpy, root, &w, &w, &pointer_x, &pointer_y, &_, &_, &_);
        if (follow_mouse == -1) {
            // follow the mouse, put it at center of grabbing region
            x_off += pointer_x - s->width  / 2 - x_off;
            y_off += pointer_y - s->height / 2 - y_off;
        } else {
            // follow the mouse, but only move the grabbing region when mouse
            // reaches within certain pixels to the edge.
            if (pointer_x > x_off + s->width - follow_mouse) {
                x_off += pointer_x - (x_off + s->width - follow_mouse);
            } else if (pointer_x < x_off + follow_mouse)
                x_off -= (x_off + follow_mouse) - pointer_x;
            if (pointer_y > y_off + s->height - follow_mouse) {
                y_off += pointer_y - (y_off + s->height - follow_mouse);
            } else if (pointer_y < y_off + follow_mouse)
                y_off -= (y_off + follow_mouse) - pointer_y;
        }
        // adjust grabbing region position if it goes out of screen.
        s->x_off = x_off = FFMIN(FFMAX(x_off, 0), screen_w - s->width);
        s->y_off = y_off = FFMIN(FFMAX(y_off, 0), screen_h - s->height);
    }

456
    if(s->use_shm) {
457
        if (!XShmGetImage(dpy, root, image, x_off, y_off, AllPlanes)) {
458 459 460
            av_log (s1, AV_LOG_INFO, "XShmGetImage() failed\n");
        }
    } else {
461
        if (!xget_zpixmap(dpy, root, image, x_off, y_off)) {
462 463 464 465
            av_log (s1, AV_LOG_INFO, "XGetZPixmap() failed\n");
        }
    }

466
    if (s->draw_mouse) {
Roxis's avatar
Roxis committed
467
        paint_mouse_pointer(image, s);
468 469 470
    }

    return s->frame_size;
471 472
}

Edouard Gomez's avatar
Edouard Gomez committed
473
/**
474
 * Close x11 frame grabber (public device demuxer API).
Edouard Gomez's avatar
Edouard Gomez committed
475 476 477 478
 *
 * @param s1 Context from avformat core
 * @return 0 success, !0 failure
 */
479 480
static int
x11grab_read_close(AVFormatContext *s1)
481
{
482
    struct x11_grab *x11grab = s1->priv_data;
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499

    /* Detach cleanly from shared mem */
    if (x11grab->use_shm) {
        XShmDetach(x11grab->dpy, &x11grab->shminfo);
        shmdt(x11grab->shminfo.shmaddr);
        shmctl(x11grab->shminfo.shmid, IPC_RMID, NULL);
    }

    /* Destroy X11 image */
    if (x11grab->image) {
        XDestroyImage(x11grab->image);
        x11grab->image = NULL;
    }

    /* Free X11 display */
    XCloseDisplay(x11grab->dpy);
    return 0;
500 501
}

502 503 504 505
#define OFFSET(x) offsetof(struct x11_grab, x)
#define DEC AV_OPT_FLAG_DECODING_PARAM
static const AVOption options[] = {
    { "video_size", "A string describing frame size, such as 640x480 or hd720.", OFFSET(video_size), FF_OPT_TYPE_STRING, {.str = "vga"}, 0, 0, DEC },
506
    { "framerate", "", OFFSET(framerate), FF_OPT_TYPE_STRING, {.str = "ntsc"}, 0, 0, DEC },
507
    { "draw_mouse", "Draw the mouse pointer.", OFFSET(draw_mouse), FF_OPT_TYPE_INT, { 1 }, 0, 1, DEC },
508 509 510
    { "follow_mouse", "Move the grabbing region when the mouse pointer reaches within specified amount of pixels to the edge of region.",
      OFFSET(follow_mouse), FF_OPT_TYPE_INT, { 0 }, -1, INT_MAX, DEC, "follow_mouse" },
    { "centered", "Keep the mouse pointer at the center of grabbing region when following.", 0, FF_OPT_TYPE_CONST, { -1 }, INT_MIN, INT_MAX, DEC, "follow_mouse" },
511 512 513 514 515 516 517 518 519 520
    { NULL },
};

static const AVClass x11_class = {
    .class_name = "X11grab indev",
    .item_name  = av_default_item_name,
    .option     = options,
    .version    = LIBAVUTIL_VERSION_INT,
};

Edouard Gomez's avatar
Edouard Gomez committed
521
/** x11 grabber device demuxer declaration */
522
AVInputFormat ff_x11_grab_device_demuxer =
523
{
524
    "x11grab",
525
    NULL_IF_CONFIG_SMALL("X11grab"),
526
    sizeof(struct x11_grab),
527 528 529 530 531
    NULL,
    x11grab_read_header,
    x11grab_read_packet,
    x11grab_read_close,
    .flags = AVFMT_NOFILE,
532
    .priv_class = &x11_class,
533
};