x11grab.c 16.5 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 34 35 36 37
/**
 * @file x11grab.c
 * X11 frame device demuxer by Clemens Fruhwirth <clemens@endorphin.org>
 * and Edouard Gomez <ed.gomez@free.fr>.
 */

38 39 40 41 42 43 44 45 46 47 48 49
#include "avformat.h"
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/time.h>
#define _LINUX_TIME_H 1
#include <time.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xlibint.h>
#include <X11/Xproto.h>
50
#include <X11/Xutil.h>
51 52 53 54
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>

Edouard Gomez's avatar
Edouard Gomez committed
55 56 57 58
/**
 * X11 Device Demuxer context
 */
typedef struct x11_grab_s
59
{
Edouard Gomez's avatar
Edouard Gomez committed
60 61 62 63 64 65 66 67 68 69 70 71 72
    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 */
73
    int mouse_warning_shown;
Edouard Gomez's avatar
Edouard Gomez committed
74 75 76 77 78 79 80 81
} x11_grab_t;

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

100
    param = av_strdup(s1->filename);
101 102 103 104 105 106
    offset = strchr(param, '+');
    if (offset) {
        sscanf(offset, "%d,%d", &x_off, &y_off);
        *offset= 0;
    }

107
    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);
108 109 110 111

    dpy = XOpenDisplay(param);
    if(!dpy) {
        av_log(s1, AV_LOG_ERROR, "Could not open X display.\n");
112
        return AVERROR(EIO);
113
    }
114 115

    if (!ap || ap->width <= 0 || ap->height <= 0 || ap->time_base.den <= 0) {
Benoit Fouet's avatar
Benoit Fouet committed
116
        av_log(s1, AV_LOG_ERROR, "AVParameters don't have video size and/or rate. Use -s and -r.\n");
117
        return AVERROR(EIO);
118 119 120 121
    }

    st = av_new_stream(s1, 0);
    if (!st) {
122
        return AVERROR(ENOMEM);
123 124 125 126
    }
    av_set_pts_info(st, 64, 1, 1000000); /* 64 bits pts in us */

    use_shm = XShmQueryExtension(dpy);
Edouard Gomez's avatar
Edouard Gomez committed
127
    av_log(s1, AV_LOG_INFO, "shared memory extension %s found\n", use_shm ? "" : "not");
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142

    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");
143
            return AVERROR(ENOMEM);
144 145 146 147 148 149 150
        }
        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 :) */
151
            return AVERROR(EIO);
152 153 154 155 156 157 158 159 160 161
        }
    } 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
162
        av_log (s1, AV_LOG_DEBUG, "8 bit palette\n");
163 164 165
        input_pixfmt = PIX_FMT_PAL8;
        break;
    case 16:
Edouard Gomez's avatar
Edouard Gomez committed
166 167 168
        if (       image->red_mask   == 0xf800 &&
                   image->green_mask == 0x07e0 &&
                   image->blue_mask  == 0x001f ) {
169 170
            av_log (s1, AV_LOG_DEBUG, "16 bit RGB565\n");
            input_pixfmt = PIX_FMT_RGB565;
Edouard Gomez's avatar
Edouard Gomez committed
171 172 173
        } else if (image->red_mask   == 0x7c00 &&
                   image->green_mask == 0x03e0 &&
                   image->blue_mask  == 0x001f ) {
174 175 176 177 178
            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);
179
            return AVERROR(EIO);
180 181 182
        }
        break;
    case 24:
Edouard Gomez's avatar
Edouard Gomez committed
183 184 185
        if (        image->red_mask   == 0xff0000 &&
                    image->green_mask == 0x00ff00 &&
                    image->blue_mask  == 0x0000ff ) {
186
            input_pixfmt = PIX_FMT_BGR24;
Edouard Gomez's avatar
Edouard Gomez committed
187 188 189
        } else if ( image->red_mask   == 0x0000ff &&
                    image->green_mask == 0x00ff00 &&
                    image->blue_mask  == 0xff0000 ) {
190 191 192 193
            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);
194
            return AVERROR(EIO);
195 196 197
        }
        break;
    case 32:
198
#if 0
199
        GetColorInfo (image, &c_info);
Edouard Gomez's avatar
Edouard Gomez committed
200
        if ( c_info.alpha_mask == 0xff000000 && image->green_mask == 0x0000ff00) {
201 202 203 204 205 206 207 208 209
            /* 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);
210
            return AVERROR(EIO);
211
        }
212
#endif
213
        input_pixfmt = PIX_FMT_RGB32;
214 215 216 217 218 219
        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
220
    x11grab->frame_size = ap->width * ap->height * image->bits_per_pixel/8;
221 222 223
    x11grab->dpy = dpy;
    x11grab->width = ap->width;
    x11grab->height = ap->height;
Edouard Gomez's avatar
Edouard Gomez committed
224 225
    x11grab->time_base  = ap->time_base;
    x11grab->time_frame = av_gettime() / av_q2d(ap->time_base);
226 227 228 229
    x11grab->x_off = x_off;
    x11grab->y_off = y_off;
    x11grab->image = image;
    x11grab->use_shm = use_shm;
230
    x11grab->mouse_warning_shown = 0;
231 232 233

    st->codec->codec_type = CODEC_TYPE_VIDEO;
    st->codec->codec_id = CODEC_ID_RAWVIDEO;
Edouard Gomez's avatar
Edouard Gomez committed
234 235
    st->codec->width = ap->width;
    st->codec->height = ap->height;
236
    st->codec->pix_fmt = input_pixfmt;
Edouard Gomez's avatar
Edouard Gomez committed
237 238
    st->codec->time_base = ap->time_base;
    st->codec->bit_rate = x11grab->frame_size * 1/av_q2d(ap->time_base) * 8;
239 240

    return 0;
241 242
}

Edouard Gomez's avatar
Edouard Gomez committed
243 244 245 246 247 248 249 250
/**
 * 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
 */
251
static void
Edouard Gomez's avatar
Edouard Gomez committed
252
get_pointer_coordinates(int *x, int *y, Display *dpy, AVFormatContext *s1)
253
{
254 255 256 257 258 259 260 261
    Window mrootwindow, childwindow;
    int dummy;

    mrootwindow = DefaultRootWindow(dpy);

    if (XQueryPointer(dpy, mrootwindow, &mrootwindow, &childwindow,
                      x, y, &dummy, &dummy, (unsigned int*)&dummy)) {
    } else {
262 263 264 265 266
        x11_grab_t *s = s1->priv_data;
        if (!s->mouse_warning_shown) {
            av_log(s1, AV_LOG_INFO, "couldn't find mouse pointer\n");
            s->mouse_warning_shown = 1;
        }
267 268 269
        *x = -1;
        *y = -1;
    }
270 271
}

Edouard Gomez's avatar
Edouard Gomez committed
272 273 274 275 276 277 278 279 280 281 282
/**
 * 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
 */
283 284 285 286 287 288 289 290 291 292 293
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
294
        *dst = !!or;
295 296 297
        break;
    }
}
298

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

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

    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
334 335
    if (   x - x_off >= 0 && x < width + x_off
        && y - y_off >= 0 && y < height + y_off) {
336
        uint8_t *im_data = (uint8_t*)image->data;
Edouard Gomez's avatar
Edouard Gomez committed
337 338
        int bytes_per_pixel;
        int line;
339
        int masks;
340

341
        /* Select correct masks and pixel size */
Edouard Gomez's avatar
Edouard Gomez committed
342
        if (image->bits_per_pixel == 8) {
343
            masks = 1;
Edouard Gomez's avatar
Edouard Gomez committed
344 345
        } else {
            masks = (image->red_mask|image->green_mask|image->blue_mask);
346
        }
Edouard Gomez's avatar
Edouard Gomez committed
347
        bytes_per_pixel = image->bits_per_pixel>>3;
348 349

        /* Shift to right line */
Edouard Gomez's avatar
Edouard Gomez committed
350 351 352
        im_data += image->bytes_per_line * (y - y_off);
        /* Shift to right pixel in the line */
        im_data += bytes_per_pixel * (x - x_off);
353 354

        /* Draw the cursor - proper loop */
Edouard Gomez's avatar
Edouard Gomez committed
355
        for (line = 0; line < FFMIN(20, (y_off + height) - y); line++) {
356
            uint8_t *cursor = im_data;
Edouard Gomez's avatar
Edouard Gomez committed
357
            int column;
358 359 360
            uint16_t bm_b;
            uint16_t bm_w;

Edouard Gomez's avatar
Edouard Gomez committed
361 362
            bm_b = mousePointerBlack[line];
            bm_w = mousePointerWhite[line];
363

Edouard Gomez's avatar
Edouard Gomez committed
364 365
            for (column = 0; column < FFMIN(16, (x_off + width) - x); column++) {
                apply_masks(cursor, ~(masks*(bm_b&1)), masks*(bm_w&1),
366
                            image->bits_per_pixel);
Edouard Gomez's avatar
Edouard Gomez committed
367
                cursor += bytes_per_pixel;
368 369 370 371
                bm_b >>= 1;
                bm_w >>= 1;
            }
            im_data += image->bytes_per_line;
372 373
        }
    }
374 375 376
}


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

    if (!image) {
Edouard Gomez's avatar
Edouard Gomez committed
395
        return 0;
396 397 398 399 400 401 402 403 404 405 406 407 408 409
    }

    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
410
    if (!_XReply(dpy, (xReply *)&rep, 0, xFalse) || !rep.length) {
411 412
        UnlockDisplay(dpy);
        SyncHandle();
Edouard Gomez's avatar
Edouard Gomez committed
413
        return 0;
414 415 416 417 418 419 420
    }

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

    UnlockDisplay(dpy);
    SyncHandle();
Edouard Gomez's avatar
Edouard Gomez committed
421
    return 1;
422 423
}

Edouard Gomez's avatar
Edouard Gomez committed
424 425 426 427 428 429 430
/**
 * 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
 */
431 432
static int
x11grab_read_packet(AVFormatContext *s1, AVPacket *pkt)
433
{
Edouard Gomez's avatar
Edouard Gomez committed
434
    x11_grab_t *s = s1->priv_data;
435 436 437 438 439 440 441 442 443
    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 */
444
    s->time_frame += INT64_C(1000000);
445 446 447 448

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

    if (av_new_packet(pkt, s->frame_size) < 0) {
462
        return AVERROR(EIO);
463 464
    }

Edouard Gomez's avatar
Edouard Gomez committed
465
    pkt->pts = curtime;
466 467 468 469 470 471

    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
472
        if (!xget_zpixmap(dpy, RootWindow(dpy, DefaultScreen(dpy)), image, x_off, y_off)) {
473 474 475 476 477 478
            av_log (s1, AV_LOG_INFO, "XGetZPixmap() failed\n");
        }
    }

    {
        int pointer_x, pointer_y;
Edouard Gomez's avatar
Edouard Gomez committed
479 480
        get_pointer_coordinates(&pointer_x, &pointer_y, dpy, s1);
        paint_mouse_pointer(image, s, pointer_x, pointer_y);
481 482 483 484 485 486
    }


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

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

    /* 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;
516 517
}

Edouard Gomez's avatar
Edouard Gomez committed
518
/** x11 grabber device demuxer declaration */
519 520
AVInputFormat x11_grab_device_demuxer =
{
521 522
    "x11grab",
    "X11grab",
Edouard Gomez's avatar
Edouard Gomez committed
523
    sizeof(x11_grab_t),
524 525 526 527 528
    NULL,
    x11grab_read_header,
    x11grab_read_packet,
    x11grab_read_close,
    .flags = AVFMT_NOFILE,
529
};