• Main Page
  • Related Pages
  • Modules
  • Data Structures
  • Files
  • File List
  • Globals

libavdevice/x11grab.c

Go to the documentation of this file.
00001 /*
00002  * X11 video grab interface
00003  *
00004  * This file is part of FFmpeg.
00005  *
00006  * FFmpeg integration:
00007  * Copyright (C) 2006 Clemens Fruhwirth <clemens@endorphin.org>
00008  *                    Edouard Gomez <ed.gomez@free.fr>
00009  *
00010  * This file contains code from grab.c:
00011  * Copyright (c) 2000-2001 Fabrice Bellard
00012  *
00013  * This file contains code from the xvidcap project:
00014  * Copyright (C) 1997-1998 Rasca, Berlin
00015  *               2003-2004 Karl H. Beckers, Frankfurt
00016  *
00017  * FFmpeg is free software; you can redistribute it and/or modify
00018  * it under the terms of the GNU General Public License as published by
00019  * the Free Software Foundation; either version 2 of the License, or
00020  * (at your option) any later version.
00021  *
00022  * FFmpeg is distributed in the hope that it will be useful,
00023  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00024  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00025  * GNU General Public License for more details.
00026  *
00027  * You should have received a copy of the GNU General Public License
00028  * along with FFmpeg; if not, write to the Free Software
00029  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
00030  */
00031 
00039 #include "config.h"
00040 #include "libavformat/internal.h"
00041 #include "libavutil/log.h"
00042 #include "libavutil/opt.h"
00043 #include "libavutil/parseutils.h"
00044 #include <time.h>
00045 #include <X11/X.h>
00046 #include <X11/Xlib.h>
00047 #include <X11/Xlibint.h>
00048 #include <X11/Xproto.h>
00049 #include <X11/Xutil.h>
00050 #include <sys/shm.h>
00051 #include <X11/extensions/shape.h>
00052 #include <X11/extensions/XShm.h>
00053 #include <X11/extensions/Xfixes.h>
00054 #include "avdevice.h"
00055 
00059 struct x11_grab
00060 {
00061     const AVClass *class;    
00062     int frame_size;          
00063     AVRational time_base;    
00064     int64_t time_frame;      
00066     char *video_size;        
00067     int height;              
00068     int width;               
00069     int x_off;               
00070     int y_off;               
00072     Display *dpy;            
00073     XImage *image;           
00074     int use_shm;             
00075     XShmSegmentInfo shminfo; 
00076     int  draw_mouse;         
00077     int  follow_mouse;       
00078     int  show_region;        
00079     char *framerate;         
00081     Window region_win;       
00082 };
00083 
00084 #define REGION_WIN_BORDER 3
00085 
00090 static void
00091 x11grab_draw_region_win(struct x11_grab *s)
00092 {
00093     Display *dpy = s->dpy;
00094     int screen;
00095     Window win = s->region_win;
00096     GC gc;
00097 
00098     screen = DefaultScreen(dpy);
00099     gc = XCreateGC(dpy, win, 0, 0);
00100     XSetForeground(dpy, gc, WhitePixel(dpy, screen));
00101     XSetBackground(dpy, gc, BlackPixel(dpy, screen));
00102     XSetLineAttributes(dpy, gc, REGION_WIN_BORDER, LineDoubleDash, 0, 0);
00103     XDrawRectangle(dpy, win, gc,
00104                    1, 1,
00105                    (s->width  + REGION_WIN_BORDER * 2) - 1 * 2 - 1,
00106                    (s->height + REGION_WIN_BORDER * 2) - 1 * 2 - 1);
00107     XFreeGC(dpy, gc);
00108 }
00109 
00115 static void
00116 x11grab_region_win_init(struct x11_grab *s)
00117 {
00118     Display *dpy = s->dpy;
00119     int screen;
00120     XSetWindowAttributes attribs;
00121     XRectangle rect;
00122 
00123     screen = DefaultScreen(dpy);
00124     attribs.override_redirect = True;
00125     s->region_win = XCreateWindow(dpy, RootWindow(dpy, screen),
00126                                   s->x_off  - REGION_WIN_BORDER,
00127                                   s->y_off  - REGION_WIN_BORDER,
00128                                   s->width  + REGION_WIN_BORDER * 2,
00129                                   s->height + REGION_WIN_BORDER * 2,
00130                                   0, CopyFromParent,
00131                                   InputOutput, CopyFromParent,
00132                                   CWOverrideRedirect, &attribs);
00133     rect.x = 0;
00134     rect.y = 0;
00135     rect.width  = s->width;
00136     rect.height = s->height;
00137     XShapeCombineRectangles(dpy, s->region_win,
00138                             ShapeBounding, REGION_WIN_BORDER, REGION_WIN_BORDER,
00139                             &rect, 1, ShapeSubtract, 0);
00140     XMapWindow(dpy, s->region_win);
00141     XSelectInput(dpy, s->region_win, ExposureMask | StructureNotifyMask);
00142     x11grab_draw_region_win(s);
00143 }
00144 
00156 static int
00157 x11grab_read_header(AVFormatContext *s1, AVFormatParameters *ap)
00158 {
00159     struct x11_grab *x11grab = s1->priv_data;
00160     Display *dpy;
00161     AVStream *st = NULL;
00162     enum PixelFormat input_pixfmt;
00163     XImage *image;
00164     int x_off = 0;
00165     int y_off = 0;
00166     int screen;
00167     int use_shm;
00168     char *dpyname, *offset;
00169     int ret = 0;
00170     AVRational framerate;
00171 
00172     dpyname = av_strdup(s1->filename);
00173     offset = strchr(dpyname, '+');
00174     if (offset) {
00175         sscanf(offset, "%d,%d", &x_off, &y_off);
00176         x11grab->draw_mouse = !strstr(offset, "nomouse");
00177         *offset= 0;
00178     }
00179 
00180     if ((ret = av_parse_video_size(&x11grab->width, &x11grab->height, x11grab->video_size)) < 0) {
00181         av_log(s1, AV_LOG_ERROR, "Couldn't parse video size.\n");
00182         goto out;
00183     }
00184     if ((ret = av_parse_video_rate(&framerate, x11grab->framerate)) < 0) {
00185         av_log(s1, AV_LOG_ERROR, "Could not parse framerate: %s.\n", x11grab->framerate);
00186         goto out;
00187     }
00188     av_log(s1, AV_LOG_INFO, "device: %s -> display: %s x: %d y: %d width: %d height: %d\n",
00189            s1->filename, dpyname, x_off, y_off, x11grab->width, x11grab->height);
00190 
00191     dpy = XOpenDisplay(dpyname);
00192     av_freep(&dpyname);
00193     if(!dpy) {
00194         av_log(s1, AV_LOG_ERROR, "Could not open X display.\n");
00195         ret = AVERROR(EIO);
00196         goto out;
00197     }
00198 
00199     st = avformat_new_stream(s1, NULL);
00200     if (!st) {
00201         ret = AVERROR(ENOMEM);
00202         goto out;
00203     }
00204     avpriv_set_pts_info(st, 64, 1, 1000000); /* 64 bits pts in us */
00205 
00206     screen = DefaultScreen(dpy);
00207 
00208     if (x11grab->follow_mouse) {
00209         int screen_w, screen_h;
00210         Window w;
00211 
00212         screen_w = DisplayWidth(dpy, screen);
00213         screen_h = DisplayHeight(dpy, screen);
00214         XQueryPointer(dpy, RootWindow(dpy, screen), &w, &w, &x_off, &y_off, &ret, &ret, &ret);
00215         x_off -= x11grab->width / 2;
00216         y_off -= x11grab->height / 2;
00217         x_off = FFMIN(FFMAX(x_off, 0), screen_w - x11grab->width);
00218         y_off = FFMIN(FFMAX(y_off, 0), screen_h - x11grab->height);
00219         av_log(s1, AV_LOG_INFO, "followmouse is enabled, resetting grabbing region to x: %d y: %d\n", x_off, y_off);
00220     }
00221 
00222     use_shm = XShmQueryExtension(dpy);
00223     av_log(s1, AV_LOG_INFO, "shared memory extension%s found\n", use_shm ? "" : " not");
00224 
00225     if(use_shm) {
00226         int scr = XDefaultScreen(dpy);
00227         image = XShmCreateImage(dpy,
00228                                 DefaultVisual(dpy, scr),
00229                                 DefaultDepth(dpy, scr),
00230                                 ZPixmap,
00231                                 NULL,
00232                                 &x11grab->shminfo,
00233                                 x11grab->width, x11grab->height);
00234         x11grab->shminfo.shmid = shmget(IPC_PRIVATE,
00235                                         image->bytes_per_line * image->height,
00236                                         IPC_CREAT|0777);
00237         if (x11grab->shminfo.shmid == -1) {
00238             av_log(s1, AV_LOG_ERROR, "Fatal: Can't get shared memory!\n");
00239             ret = AVERROR(ENOMEM);
00240             goto out;
00241         }
00242         x11grab->shminfo.shmaddr = image->data = shmat(x11grab->shminfo.shmid, 0, 0);
00243         x11grab->shminfo.readOnly = False;
00244 
00245         if (!XShmAttach(dpy, &x11grab->shminfo)) {
00246             av_log(s1, AV_LOG_ERROR, "Fatal: Failed to attach shared memory!\n");
00247             /* needs some better error subroutine :) */
00248             ret = AVERROR(EIO);
00249             goto out;
00250         }
00251     } else {
00252         image = XGetImage(dpy, RootWindow(dpy, screen),
00253                           x_off,y_off,
00254                           x11grab->width, x11grab->height,
00255                           AllPlanes, ZPixmap);
00256     }
00257 
00258     switch (image->bits_per_pixel) {
00259     case 8:
00260         av_log (s1, AV_LOG_DEBUG, "8 bit palette\n");
00261         input_pixfmt = PIX_FMT_PAL8;
00262         break;
00263     case 16:
00264         if (       image->red_mask   == 0xf800 &&
00265                    image->green_mask == 0x07e0 &&
00266                    image->blue_mask  == 0x001f ) {
00267             av_log (s1, AV_LOG_DEBUG, "16 bit RGB565\n");
00268             input_pixfmt = PIX_FMT_RGB565;
00269         } else if (image->red_mask   == 0x7c00 &&
00270                    image->green_mask == 0x03e0 &&
00271                    image->blue_mask  == 0x001f ) {
00272             av_log(s1, AV_LOG_DEBUG, "16 bit RGB555\n");
00273             input_pixfmt = PIX_FMT_RGB555;
00274         } else {
00275             av_log(s1, AV_LOG_ERROR, "RGB ordering at image depth %i not supported ... aborting\n", image->bits_per_pixel);
00276             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);
00277             ret = AVERROR(EIO);
00278             goto out;
00279         }
00280         break;
00281     case 24:
00282         if (        image->red_mask   == 0xff0000 &&
00283                     image->green_mask == 0x00ff00 &&
00284                     image->blue_mask  == 0x0000ff ) {
00285             input_pixfmt = PIX_FMT_BGR24;
00286         } else if ( image->red_mask   == 0x0000ff &&
00287                     image->green_mask == 0x00ff00 &&
00288                     image->blue_mask  == 0xff0000 ) {
00289             input_pixfmt = PIX_FMT_RGB24;
00290         } else {
00291             av_log(s1, AV_LOG_ERROR,"rgb ordering at image depth %i not supported ... aborting\n", image->bits_per_pixel);
00292             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);
00293             ret = AVERROR(EIO);
00294             goto out;
00295         }
00296         break;
00297     case 32:
00298         input_pixfmt = PIX_FMT_RGB32;
00299         break;
00300     default:
00301         av_log(s1, AV_LOG_ERROR, "image depth %i not supported ... aborting\n", image->bits_per_pixel);
00302         ret = AVERROR(EINVAL);
00303         goto out;
00304     }
00305 
00306     x11grab->frame_size = x11grab->width * x11grab->height * image->bits_per_pixel/8;
00307     x11grab->dpy = dpy;
00308     x11grab->time_base  = (AVRational){framerate.den, framerate.num};
00309     x11grab->time_frame = av_gettime() / av_q2d(x11grab->time_base);
00310     x11grab->x_off = x_off;
00311     x11grab->y_off = y_off;
00312     x11grab->image = image;
00313     x11grab->use_shm = use_shm;
00314 
00315     st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
00316     st->codec->codec_id = CODEC_ID_RAWVIDEO;
00317     st->codec->width  = x11grab->width;
00318     st->codec->height = x11grab->height;
00319     st->codec->pix_fmt = input_pixfmt;
00320     st->codec->time_base = x11grab->time_base;
00321     st->codec->bit_rate = x11grab->frame_size * 1/av_q2d(x11grab->time_base) * 8;
00322 
00323 out:
00324     return ret;
00325 }
00326 
00334 static void
00335 paint_mouse_pointer(XImage *image, struct x11_grab *s)
00336 {
00337     int x_off = s->x_off;
00338     int y_off = s->y_off;
00339     int width = s->width;
00340     int height = s->height;
00341     Display *dpy = s->dpy;
00342     XFixesCursorImage *xcim;
00343     int x, y;
00344     int line, column;
00345     int to_line, to_column;
00346     int pixstride = image->bits_per_pixel >> 3;
00347     /* Warning: in its insanity, xlib provides unsigned image data through a
00348      * char* pointer, so we have to make it uint8_t to make things not break.
00349      * Anyone who performs further investigation of the xlib API likely risks
00350      * permanent brain damage. */
00351     uint8_t *pix = image->data;
00352 
00353     /* Code doesn't currently support 16-bit or PAL8 */
00354     if (image->bits_per_pixel != 24 && image->bits_per_pixel != 32)
00355         return;
00356 
00357     xcim = XFixesGetCursorImage(dpy);
00358 
00359     x = xcim->x - xcim->xhot;
00360     y = xcim->y - xcim->yhot;
00361 
00362     to_line = FFMIN((y + xcim->height), (height + y_off));
00363     to_column = FFMIN((x + xcim->width), (width + x_off));
00364 
00365     for (line = FFMAX(y, y_off); line < to_line; line++) {
00366         for (column = FFMAX(x, x_off); column < to_column; column++) {
00367             int  xcim_addr = (line - y) * xcim->width + column - x;
00368             int image_addr = ((line - y_off) * width + column - x_off) * pixstride;
00369             int r = (uint8_t)(xcim->pixels[xcim_addr] >>  0);
00370             int g = (uint8_t)(xcim->pixels[xcim_addr] >>  8);
00371             int b = (uint8_t)(xcim->pixels[xcim_addr] >> 16);
00372             int a = (uint8_t)(xcim->pixels[xcim_addr] >> 24);
00373 
00374             if (a == 255) {
00375                 pix[image_addr+0] = r;
00376                 pix[image_addr+1] = g;
00377                 pix[image_addr+2] = b;
00378             } else if (a) {
00379                 /* pixel values from XFixesGetCursorImage come premultiplied by alpha */
00380                 pix[image_addr+0] = r + (pix[image_addr+0]*(255-a) + 255/2) / 255;
00381                 pix[image_addr+1] = g + (pix[image_addr+1]*(255-a) + 255/2) / 255;
00382                 pix[image_addr+2] = b + (pix[image_addr+2]*(255-a) + 255/2) / 255;
00383             }
00384         }
00385     }
00386 
00387     XFree(xcim);
00388     xcim = NULL;
00389 }
00390 
00391 
00402 static int
00403 xget_zpixmap(Display *dpy, Drawable d, XImage *image, int x, int y)
00404 {
00405     xGetImageReply rep;
00406     xGetImageReq *req;
00407     long nbytes;
00408 
00409     if (!image) {
00410         return 0;
00411     }
00412 
00413     LockDisplay(dpy);
00414     GetReq(GetImage, req);
00415 
00416     /* First set up the standard stuff in the request */
00417     req->drawable = d;
00418     req->x = x;
00419     req->y = y;
00420     req->width = image->width;
00421     req->height = image->height;
00422     req->planeMask = (unsigned int)AllPlanes;
00423     req->format = ZPixmap;
00424 
00425     if (!_XReply(dpy, (xReply *)&rep, 0, xFalse) || !rep.length) {
00426         UnlockDisplay(dpy);
00427         SyncHandle();
00428         return 0;
00429     }
00430 
00431     nbytes = (long)rep.length << 2;
00432     _XReadPad(dpy, image->data, nbytes);
00433 
00434     UnlockDisplay(dpy);
00435     SyncHandle();
00436     return 1;
00437 }
00438 
00446 static int
00447 x11grab_read_packet(AVFormatContext *s1, AVPacket *pkt)
00448 {
00449     struct x11_grab *s = s1->priv_data;
00450     Display *dpy = s->dpy;
00451     XImage *image = s->image;
00452     int x_off = s->x_off;
00453     int y_off = s->y_off;
00454 
00455     int screen;
00456     Window root;
00457     int follow_mouse = s->follow_mouse;
00458 
00459     int64_t curtime, delay;
00460     struct timespec ts;
00461 
00462     /* Calculate the time of the next frame */
00463     s->time_frame += INT64_C(1000000);
00464 
00465     /* wait based on the frame rate */
00466     for(;;) {
00467         curtime = av_gettime();
00468         delay = s->time_frame * av_q2d(s->time_base) - curtime;
00469         if (delay <= 0) {
00470             if (delay < INT64_C(-1000000) * av_q2d(s->time_base)) {
00471                 s->time_frame += INT64_C(1000000);
00472             }
00473             break;
00474         }
00475         ts.tv_sec = delay / 1000000;
00476         ts.tv_nsec = (delay % 1000000) * 1000;
00477         nanosleep(&ts, NULL);
00478     }
00479 
00480     av_init_packet(pkt);
00481     pkt->data = image->data;
00482     pkt->size = s->frame_size;
00483     pkt->pts = curtime;
00484 
00485     screen = DefaultScreen(dpy);
00486     root = RootWindow(dpy, screen);
00487     if (follow_mouse) {
00488         int screen_w, screen_h;
00489         int pointer_x, pointer_y, _;
00490         Window w;
00491 
00492         screen_w = DisplayWidth(dpy, screen);
00493         screen_h = DisplayHeight(dpy, screen);
00494         XQueryPointer(dpy, root, &w, &w, &pointer_x, &pointer_y, &_, &_, &_);
00495         if (follow_mouse == -1) {
00496             // follow the mouse, put it at center of grabbing region
00497             x_off += pointer_x - s->width  / 2 - x_off;
00498             y_off += pointer_y - s->height / 2 - y_off;
00499         } else {
00500             // follow the mouse, but only move the grabbing region when mouse
00501             // reaches within certain pixels to the edge.
00502             if (pointer_x > x_off + s->width - follow_mouse) {
00503                 x_off += pointer_x - (x_off + s->width - follow_mouse);
00504             } else if (pointer_x < x_off + follow_mouse)
00505                 x_off -= (x_off + follow_mouse) - pointer_x;
00506             if (pointer_y > y_off + s->height - follow_mouse) {
00507                 y_off += pointer_y - (y_off + s->height - follow_mouse);
00508             } else if (pointer_y < y_off + follow_mouse)
00509                 y_off -= (y_off + follow_mouse) - pointer_y;
00510         }
00511         // adjust grabbing region position if it goes out of screen.
00512         s->x_off = x_off = FFMIN(FFMAX(x_off, 0), screen_w - s->width);
00513         s->y_off = y_off = FFMIN(FFMAX(y_off, 0), screen_h - s->height);
00514 
00515         if (s->show_region && s->region_win)
00516             XMoveWindow(dpy, s->region_win,
00517                         s->x_off - REGION_WIN_BORDER,
00518                         s->y_off - REGION_WIN_BORDER);
00519     }
00520 
00521     if (s->show_region) {
00522         if (s->region_win) {
00523             XEvent evt;
00524             // clean up the events, and do the initinal draw or redraw.
00525             for (evt.type = NoEventMask; XCheckMaskEvent(dpy, ExposureMask | StructureNotifyMask, &evt); );
00526             if (evt.type)
00527                 x11grab_draw_region_win(s);
00528         } else {
00529             x11grab_region_win_init(s);
00530         }
00531     }
00532 
00533     if(s->use_shm) {
00534         if (!XShmGetImage(dpy, root, image, x_off, y_off, AllPlanes)) {
00535             av_log (s1, AV_LOG_INFO, "XShmGetImage() failed\n");
00536         }
00537     } else {
00538         if (!xget_zpixmap(dpy, root, image, x_off, y_off)) {
00539             av_log (s1, AV_LOG_INFO, "XGetZPixmap() failed\n");
00540         }
00541     }
00542     if (image->bits_per_pixel == 32)
00543         XAddPixel(image, 0xFF000000);
00544 
00545     if (s->draw_mouse) {
00546         paint_mouse_pointer(image, s);
00547     }
00548 
00549     return s->frame_size;
00550 }
00551 
00558 static int
00559 x11grab_read_close(AVFormatContext *s1)
00560 {
00561     struct x11_grab *x11grab = s1->priv_data;
00562 
00563     /* Detach cleanly from shared mem */
00564     if (x11grab->use_shm) {
00565         XShmDetach(x11grab->dpy, &x11grab->shminfo);
00566         shmdt(x11grab->shminfo.shmaddr);
00567         shmctl(x11grab->shminfo.shmid, IPC_RMID, NULL);
00568     }
00569 
00570     /* Destroy X11 image */
00571     if (x11grab->image) {
00572         XDestroyImage(x11grab->image);
00573         x11grab->image = NULL;
00574     }
00575 
00576     if (x11grab->region_win) {
00577         XDestroyWindow(x11grab->dpy, x11grab->region_win);
00578     }
00579 
00580     /* Free X11 display */
00581     XCloseDisplay(x11grab->dpy);
00582     return 0;
00583 }
00584 
00585 #define OFFSET(x) offsetof(struct x11_grab, x)
00586 #define DEC AV_OPT_FLAG_DECODING_PARAM
00587 static const AVOption options[] = {
00588     { "video_size", "A string describing frame size, such as 640x480 or hd720.", OFFSET(video_size), AV_OPT_TYPE_STRING, {.str = "vga"}, 0, 0, DEC },
00589     { "framerate", "", OFFSET(framerate), AV_OPT_TYPE_STRING, {.str = "ntsc"}, 0, 0, DEC },
00590     { "draw_mouse", "Draw the mouse pointer.", OFFSET(draw_mouse), AV_OPT_TYPE_INT, { 1 }, 0, 1, DEC },
00591     { "follow_mouse", "Move the grabbing region when the mouse pointer reaches within specified amount of pixels to the edge of region.",
00592       OFFSET(follow_mouse), AV_OPT_TYPE_INT, { 0 }, -1, INT_MAX, DEC, "follow_mouse" },
00593     { "centered", "Keep the mouse pointer at the center of grabbing region when following.", 0, AV_OPT_TYPE_CONST, { -1 }, INT_MIN, INT_MAX, DEC, "follow_mouse" },
00594     { "show_region", "Show the grabbing region.", OFFSET(show_region), AV_OPT_TYPE_INT, { 0 }, 0, 1, DEC },
00595     { NULL },
00596 };
00597 
00598 static const AVClass x11_class = {
00599     .class_name = "X11grab indev",
00600     .item_name  = av_default_item_name,
00601     .option     = options,
00602     .version    = LIBAVUTIL_VERSION_INT,
00603 };
00604 
00606 AVInputFormat ff_x11_grab_device_demuxer = {
00607     .name           = "x11grab",
00608     .long_name      = NULL_IF_CONFIG_SMALL("X11grab"),
00609     .priv_data_size = sizeof(struct x11_grab),
00610     .read_header    = x11grab_read_header,
00611     .read_packet    = x11grab_read_packet,
00612     .read_close     = x11grab_read_close,
00613     .flags          = AVFMT_NOFILE,
00614     .priv_class     = &x11_class,
00615 };
Generated on Fri Feb 1 2013 14:34:48 for FFmpeg by doxygen 1.7.1