00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
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);
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
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
00348
00349
00350
00351 uint8_t *pix = image->data;
00352
00353
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
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
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
00463 s->time_frame += INT64_C(1000000);
00464
00465
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
00497 x_off += pointer_x - s->width / 2 - x_off;
00498 y_off += pointer_y - s->height / 2 - y_off;
00499 } else {
00500
00501
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
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
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
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
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
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 };