x11grab.c 16.4 KB
Newer Older
1 2
/*
 * X11 video grab interface
3
 *
4
 * This file is part of FFmpeg.
5
 *
6 7 8
 * FFmpeg integration:
 * 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
 * FFmpeg 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
 * FFmpeg 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 28 29
 * You should have received a copy of the GNU General Public License
 * along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
30
 */
31

Edouard Gomez's avatar
Edouard Gomez committed
32
/**
33
 * @file libavdevice/x11grab.c
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 43 44
#include <time.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xlibint.h>
#include <X11/Xproto.h>
45
#include <X11/Xutil.h>
46 47 48
#include <sys/shm.h>
#include <X11/extensions/XShm.h>

Edouard Gomez's avatar
Edouard Gomez committed
49 50 51
/**
 * X11 Device Demuxer context
 */
52
struct x11_grab
53
{
Edouard Gomez's avatar
Edouard Gomez committed
54 55 56 57 58 59 60 61 62 63 64 65 66
    int frame_size;          /**< Size in bytes of a grabbed frame */
    AVRational time_base;    /**< Time base */
    int64_t time_frame;      /**< Current time */

    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 */
67
    int mouse_warning_shown;
68
};
Edouard Gomez's avatar
Edouard Gomez committed
69 70 71 72 73 74 75

/**
 * Initializes the x11 grab device demuxer (public device demuxer API).
 *
 * @param s1 Context from avformat core
 * @param ap Parameters from avformat core
 * @return <ul>
76
 *          <li>AVERROR(ENOMEM) no memory left</li>
77
 *          <li>AVERROR(EIO) other failure case</li>
Edouard Gomez's avatar
Edouard Gomez committed
78 79 80
 *          <li>0 success</li>
 *         </ul>
 */
81 82
static int
x11grab_read_header(AVFormatContext *s1, AVFormatParameters *ap)
83
{
84
    struct x11_grab *x11grab = s1->priv_data;
85 86 87 88
    Display *dpy;
    AVStream *st = NULL;
    int input_pixfmt;
    XImage *image;
Edouard Gomez's avatar
Edouard Gomez committed
89 90
    int x_off = 0;
    int y_off = 0;
91
    int use_shm;
92
    char *param, *offset;
93

94
    param = av_strdup(s1->filename);
95 96 97 98 99 100
    offset = strchr(param, '+');
    if (offset) {
        sscanf(offset, "%d,%d", &x_off, &y_off);
        *offset= 0;
    }

101
    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, ap->width, ap->height);
102 103 104 105

    dpy = XOpenDisplay(param);
    if(!dpy) {
        av_log(s1, AV_LOG_ERROR, "Could not open X display.\n");
106
        return AVERROR(EIO);
107
    }
108 109

    if (!ap || ap->width <= 0 || ap->height <= 0 || ap->time_base.den <= 0) {
Benoit Fouet's avatar
Benoit Fouet committed
110
        av_log(s1, AV_LOG_ERROR, "AVParameters don't have video size and/or rate. Use -s and -r.\n");
111
        return AVERROR(EIO);
112 113 114 115
    }

    st = av_new_stream(s1, 0);
    if (!st) {
116
        return AVERROR(ENOMEM);
117 118 119 120
    }
    av_set_pts_info(st, 64, 1, 1000000); /* 64 bits pts in us */

    use_shm = XShmQueryExtension(dpy);
Edouard Gomez's avatar
Edouard Gomez committed
121
    av_log(s1, AV_LOG_INFO, "shared memory extension %s found\n", use_shm ? "" : "not");
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136

    if(use_shm) {
        int scr = XDefaultScreen(dpy);
        image = XShmCreateImage(dpy,
                                DefaultVisual(dpy, scr),
                                DefaultDepth(dpy, scr),
                                ZPixmap,
                                NULL,
                                &x11grab->shminfo,
                                ap->width, ap->height);
        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");
137
            return AVERROR(ENOMEM);
138 139 140 141 142 143 144
        }
        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 :) */
145
            return AVERROR(EIO);
146 147 148 149 150 151 152 153 154 155
        }
    } else {
        image = XGetImage(dpy, RootWindow(dpy, DefaultScreen(dpy)),
                          x_off,y_off,
                          ap->width,ap->height,
                          AllPlanes, ZPixmap);
    }

    switch (image->bits_per_pixel) {
    case 8:
Diego Biurrun's avatar
Diego Biurrun committed
156
        av_log (s1, AV_LOG_DEBUG, "8 bit palette\n");
157 158 159
        input_pixfmt = PIX_FMT_PAL8;
        break;
    case 16:
Edouard Gomez's avatar
Edouard Gomez committed
160 161 162
        if (       image->red_mask   == 0xf800 &&
                   image->green_mask == 0x07e0 &&
                   image->blue_mask  == 0x001f ) {
163 164
            av_log (s1, AV_LOG_DEBUG, "16 bit RGB565\n");
            input_pixfmt = PIX_FMT_RGB565;
Edouard Gomez's avatar
Edouard Gomez committed
165 166 167
        } else if (image->red_mask   == 0x7c00 &&
                   image->green_mask == 0x03e0 &&
                   image->blue_mask  == 0x001f ) {
168 169 170 171 172
            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);
173
            return AVERROR(EIO);
174 175 176
        }
        break;
    case 24:
Edouard Gomez's avatar
Edouard Gomez committed
177 178 179
        if (        image->red_mask   == 0xff0000 &&
                    image->green_mask == 0x00ff00 &&
                    image->blue_mask  == 0x0000ff ) {
180
            input_pixfmt = PIX_FMT_BGR24;
Edouard Gomez's avatar
Edouard Gomez committed
181 182 183
        } else if ( image->red_mask   == 0x0000ff &&
                    image->green_mask == 0x00ff00 &&
                    image->blue_mask  == 0xff0000 ) {
184 185 186 187
            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);
188
            return AVERROR(EIO);
189 190 191
        }
        break;
    case 32:
192
#if 0
193
        GetColorInfo (image, &c_info);
Edouard Gomez's avatar
Edouard Gomez committed
194
        if ( c_info.alpha_mask == 0xff000000 && image->green_mask == 0x0000ff00) {
195 196 197 198 199 200 201 202 203
            /* byte order is relevant here, not endianness
             * endianness is handled by avcodec, but atm no such thing
             * as having ABGR, instead of ARGB in a word. Since we
             * need this for Solaris/SPARC, but need to do the conversion
             * for every frame we do it outside of this loop, cf. below
             * this matches both ARGB32 and ABGR32 */
            input_pixfmt = PIX_FMT_ARGB32;
        }  else {
            av_log(s1, AV_LOG_ERROR,"image depth %i not supported ... aborting\n", image->bits_per_pixel);
204
            return AVERROR(EIO);
205
        }
206
#endif
207
        input_pixfmt = PIX_FMT_RGB32;
208 209 210 211 212 213
        break;
    default:
        av_log(s1, AV_LOG_ERROR, "image depth %i not supported ... aborting\n", image->bits_per_pixel);
        return -1;
    }

Edouard Gomez's avatar
Edouard Gomez committed
214
    x11grab->frame_size = ap->width * ap->height * image->bits_per_pixel/8;
215 216 217
    x11grab->dpy = dpy;
    x11grab->width = ap->width;
    x11grab->height = ap->height;
Edouard Gomez's avatar
Edouard Gomez committed
218 219
    x11grab->time_base  = ap->time_base;
    x11grab->time_frame = av_gettime() / av_q2d(ap->time_base);
220 221 222 223
    x11grab->x_off = x_off;
    x11grab->y_off = y_off;
    x11grab->image = image;
    x11grab->use_shm = use_shm;
224
    x11grab->mouse_warning_shown = 0;
225 226 227

    st->codec->codec_type = CODEC_TYPE_VIDEO;
    st->codec->codec_id = CODEC_ID_RAWVIDEO;
Edouard Gomez's avatar
Edouard Gomez committed
228 229
    st->codec->width = ap->width;
    st->codec->height = ap->height;
230
    st->codec->pix_fmt = input_pixfmt;
Edouard Gomez's avatar
Edouard Gomez committed
231 232
    st->codec->time_base = ap->time_base;
    st->codec->bit_rate = x11grab->frame_size * 1/av_q2d(ap->time_base) * 8;
233 234

    return 0;
235 236
}

Edouard Gomez's avatar
Edouard Gomez committed
237 238 239 240 241 242 243 244
/**
 * Get pointer coordinates from X11.
 *
 * @param x Integer where horizontal coordinate will be returned
 * @param y Integer where vertical coordinate will be returned
 * @param dpy X11 display from where pointer coordinates are retrieved
 * @param s1 Context used for logging errors if necessary
 */
245
static void
Edouard Gomez's avatar
Edouard Gomez committed
246
get_pointer_coordinates(int *x, int *y, Display *dpy, AVFormatContext *s1)
247
{
248 249 250 251 252 253 254 255
    Window mrootwindow, childwindow;
    int dummy;

    mrootwindow = DefaultRootWindow(dpy);

    if (XQueryPointer(dpy, mrootwindow, &mrootwindow, &childwindow,
                      x, y, &dummy, &dummy, (unsigned int*)&dummy)) {
    } else {
256
        struct x11_grab *s = s1->priv_data;
257 258 259 260
        if (!s->mouse_warning_shown) {
            av_log(s1, AV_LOG_INFO, "couldn't find mouse pointer\n");
            s->mouse_warning_shown = 1;
        }
261 262 263
        *x = -1;
        *y = -1;
    }
264 265
}

Edouard Gomez's avatar
Edouard Gomez committed
266 267 268 269 270 271 272 273 274 275 276
/**
 * Mouse painting helper function that applies an 'and' and 'or' mask pair to
 * '*dst' pixel. It actually draws a mouse pointer pixel to grabbed frame.
 *
 * @param dst Destination pixel
 * @param and Part of the mask that must be applied using a bitwise 'and'
 *            operator
 * @param or  Part of the mask that must be applied using a bitwise 'or'
 *            operator
 * @param bits_per_pixel Bits per pixel used in the grabbed image
 */
277 278 279 280 281 282 283 284 285 286 287
static void inline
apply_masks(uint8_t *dst, int and, int or, int bits_per_pixel)
{
    switch (bits_per_pixel) {
    case 32:
        *(uint32_t*)dst = (*(uint32_t*)dst & and) | or;
        break;
    case 16:
        *(uint16_t*)dst = (*(uint16_t*)dst & and) | or;
        break;
    case 8:
Edouard Gomez's avatar
Edouard Gomez committed
288
        *dst = !!or;
289 290 291
        break;
    }
}
292

Edouard Gomez's avatar
Edouard Gomez committed
293 294 295
/**
 * Paints a mouse pointer in an X11 image.
 *
Diego Biurrun's avatar
Diego Biurrun committed
296
 * @param image image to paint the mouse pointer to
Edouard Gomez's avatar
Edouard Gomez committed
297 298 299 300 301
 * @param s context used to retrieve original grabbing rectangle
 *          coordinates
 * @param x Mouse pointer coordinate
 * @param y Mouse pointer coordinate
 */
302
static void
303
paint_mouse_pointer(XImage *image, struct x11_grab *s, int x, int y)
304
{
Edouard Gomez's avatar
Edouard Gomez committed
305
    /* 16x20x1bpp bitmap for the black channel of the mouse pointer */
306 307
    static const uint16_t const mousePointerBlack[] =
        {
308 309 310 311
            0x0000, 0x0003, 0x0005, 0x0009, 0x0011,
            0x0021, 0x0041, 0x0081, 0x0101, 0x0201,
            0x03c1, 0x0049, 0x0095, 0x0093, 0x0120,
            0x0120, 0x0240, 0x0240, 0x0380, 0x0000
312 313
        };

Edouard Gomez's avatar
Edouard Gomez committed
314
    /* 16x20x1bpp bitmap for the white channel of the mouse pointer */
315 316
    static const uint16_t const mousePointerWhite[] =
        {
317 318 319 320
            0x0000, 0x0000, 0x0002, 0x0006, 0x000e,
            0x001e, 0x003e, 0x007e, 0x00fe, 0x01fe,
            0x003e, 0x0036, 0x0062, 0x0060, 0x00c0,
            0x00c0, 0x0180, 0x0180, 0x0000, 0x0000
321 322 323 324 325 326 327
        };

    int x_off = s->x_off;
    int y_off = s->y_off;
    int width = s->width;
    int height = s->height;

Edouard Gomez's avatar
Edouard Gomez committed
328 329
    if (   x - x_off >= 0 && x < width + x_off
        && y - y_off >= 0 && y < height + y_off) {
330
        uint8_t *im_data = (uint8_t*)image->data;
Edouard Gomez's avatar
Edouard Gomez committed
331 332
        int bytes_per_pixel;
        int line;
333
        int masks;
334

335
        /* Select correct masks and pixel size */
Edouard Gomez's avatar
Edouard Gomez committed
336
        if (image->bits_per_pixel == 8) {
337
            masks = 1;
Edouard Gomez's avatar
Edouard Gomez committed
338 339
        } else {
            masks = (image->red_mask|image->green_mask|image->blue_mask);
340
        }
Edouard Gomez's avatar
Edouard Gomez committed
341
        bytes_per_pixel = image->bits_per_pixel>>3;
342 343

        /* Shift to right line */
Edouard Gomez's avatar
Edouard Gomez committed
344 345 346
        im_data += image->bytes_per_line * (y - y_off);
        /* Shift to right pixel in the line */
        im_data += bytes_per_pixel * (x - x_off);
347 348

        /* Draw the cursor - proper loop */
Edouard Gomez's avatar
Edouard Gomez committed
349
        for (line = 0; line < FFMIN(20, (y_off + height) - y); line++) {
350
            uint8_t *cursor = im_data;
Edouard Gomez's avatar
Edouard Gomez committed
351
            int column;
352 353 354
            uint16_t bm_b;
            uint16_t bm_w;

Edouard Gomez's avatar
Edouard Gomez committed
355 356
            bm_b = mousePointerBlack[line];
            bm_w = mousePointerWhite[line];
357

Edouard Gomez's avatar
Edouard Gomez committed
358 359
            for (column = 0; column < FFMIN(16, (x_off + width) - x); column++) {
                apply_masks(cursor, ~(masks*(bm_b&1)), masks*(bm_w&1),
360
                            image->bits_per_pixel);
Edouard Gomez's avatar
Edouard Gomez committed
361
                cursor += bytes_per_pixel;
362 363 364 365
                bm_b >>= 1;
                bm_w >>= 1;
            }
            im_data += image->bytes_per_line;
366 367
        }
    }
368 369 370
}


Edouard Gomez's avatar
Edouard Gomez committed
371 372 373 374
/**
 * Reads new data in the image structure.
 *
 * @param dpy X11 display to grab from
375
 * @param d
Edouard Gomez's avatar
Edouard Gomez committed
376 377 378 379
 * @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
380 381
 */
static int
Edouard Gomez's avatar
Edouard Gomez committed
382
xget_zpixmap(Display *dpy, Drawable d, XImage *image, int x, int y)
383
{
384 385 386 387 388
    xGetImageReply rep;
    xGetImageReq *req;
    long nbytes;

    if (!image) {
Edouard Gomez's avatar
Edouard Gomez committed
389
        return 0;
390 391 392 393 394 395 396 397 398 399 400 401 402 403
    }

    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
404
    if (!_XReply(dpy, (xReply *)&rep, 0, xFalse) || !rep.length) {
405 406
        UnlockDisplay(dpy);
        SyncHandle();
Edouard Gomez's avatar
Edouard Gomez committed
407
        return 0;
408 409 410 411 412 413 414
    }

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

    UnlockDisplay(dpy);
    SyncHandle();
Edouard Gomez's avatar
Edouard Gomez committed
415
    return 1;
416 417
}

Edouard Gomez's avatar
Edouard Gomez committed
418 419 420 421 422 423 424
/**
 * Grabs a frame from x11 (public device demuxer API).
 *
 * @param s1 Context from avformat core
 * @param pkt Packet holding the brabbed frame
 * @return frame size in bytes
 */
425 426
static int
x11grab_read_packet(AVFormatContext *s1, AVPacket *pkt)
427
{
428
    struct x11_grab *s = s1->priv_data;
429 430 431 432 433 434 435 436 437
    Display *dpy = s->dpy;
    XImage *image = s->image;
    int x_off = s->x_off;
    int y_off = s->y_off;

    int64_t curtime, delay;
    struct timespec ts;

    /* Calculate the time of the next frame */
438
    s->time_frame += INT64_C(1000000);
439 440 441 442

    /* wait based on the frame rate */
    for(;;) {
        curtime = av_gettime();
Edouard Gomez's avatar
Edouard Gomez committed
443
        delay = s->time_frame * av_q2d(s->time_base) - curtime;
444
        if (delay <= 0) {
445 446
            if (delay < INT64_C(-1000000) * av_q2d(s->time_base)) {
                s->time_frame += INT64_C(1000000);
447 448 449 450 451 452 453 454 455
            }
            break;
        }
        ts.tv_sec = delay / 1000000;
        ts.tv_nsec = (delay % 1000000) * 1000;
        nanosleep(&ts, NULL);
    }

    if (av_new_packet(pkt, s->frame_size) < 0) {
456
        return AVERROR(EIO);
457 458
    }

Edouard Gomez's avatar
Edouard Gomez committed
459
    pkt->pts = curtime;
460 461 462 463 464 465

    if(s->use_shm) {
        if (!XShmGetImage(dpy, RootWindow(dpy, DefaultScreen(dpy)), image, x_off, y_off, AllPlanes)) {
            av_log (s1, AV_LOG_INFO, "XShmGetImage() failed\n");
        }
    } else {
Edouard Gomez's avatar
Edouard Gomez committed
466
        if (!xget_zpixmap(dpy, RootWindow(dpy, DefaultScreen(dpy)), image, x_off, y_off)) {
467 468 469 470 471 472
            av_log (s1, AV_LOG_INFO, "XGetZPixmap() failed\n");
        }
    }

    {
        int pointer_x, pointer_y;
Edouard Gomez's avatar
Edouard Gomez committed
473 474
        get_pointer_coordinates(&pointer_x, &pointer_y, dpy, s1);
        paint_mouse_pointer(image, s, pointer_x, pointer_y);
475 476 477 478 479 480
    }


    /* XXX: avoid memcpy */
    memcpy(pkt->data, image->data, s->frame_size);
    return s->frame_size;
481 482
}

Edouard Gomez's avatar
Edouard Gomez committed
483 484 485 486 487 488
/**
 * Closes x11 frame grabber (public device demuxer API).
 *
 * @param s1 Context from avformat core
 * @return 0 success, !0 failure
 */
489 490
static int
x11grab_read_close(AVFormatContext *s1)
491
{
492
    struct x11_grab *x11grab = s1->priv_data;
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509

    /* 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;
510 511
}

Edouard Gomez's avatar
Edouard Gomez committed
512
/** x11 grabber device demuxer declaration */
513 514
AVInputFormat x11_grab_device_demuxer =
{
515
    "x11grab",
516
    NULL_IF_CONFIG_SMALL("X11grab"),
517
    sizeof(struct x11_grab),
518 519 520 521 522
    NULL,
    x11grab_read_header,
    x11grab_read_packet,
    x11grab_read_close,
    .flags = AVFMT_NOFILE,
523
};