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 int width;
00067 int height;
00068 int x_off;
00069 int y_off;
00071 Display *dpy;
00072 XImage *image;
00073 int use_shm;
00074 XShmSegmentInfo shminfo;
00075 int draw_mouse;
00076 int follow_mouse;
00077 int show_region;
00078 char *framerate;
00080 Window region_win;
00081 };
00082
00083 #define REGION_WIN_BORDER 3
00084
00089 static void
00090 x11grab_draw_region_win(struct x11_grab *s)
00091 {
00092 Display *dpy = s->dpy;
00093 int screen;
00094 Window win = s->region_win;
00095 GC gc;
00096
00097 screen = DefaultScreen(dpy);
00098 gc = XCreateGC(dpy, win, 0, 0);
00099 XSetForeground(dpy, gc, WhitePixel(dpy, screen));
00100 XSetBackground(dpy, gc, BlackPixel(dpy, screen));
00101 XSetLineAttributes(dpy, gc, REGION_WIN_BORDER, LineDoubleDash, 0, 0);
00102 XDrawRectangle(dpy, win, gc,
00103 1, 1,
00104 (s->width + REGION_WIN_BORDER * 2) - 1 * 2 - 1,
00105 (s->height + REGION_WIN_BORDER * 2) - 1 * 2 - 1);
00106 XFreeGC(dpy, gc);
00107 }
00108
00114 static void
00115 x11grab_region_win_init(struct x11_grab *s)
00116 {
00117 Display *dpy = s->dpy;
00118 int screen;
00119 XSetWindowAttributes attribs;
00120 XRectangle rect;
00121
00122 screen = DefaultScreen(dpy);
00123 attribs.override_redirect = True;
00124 s->region_win = XCreateWindow(dpy, RootWindow(dpy, screen),
00125 s->x_off - REGION_WIN_BORDER,
00126 s->y_off - REGION_WIN_BORDER,
00127 s->width + REGION_WIN_BORDER * 2,
00128 s->height + REGION_WIN_BORDER * 2,
00129 0, CopyFromParent,
00130 InputOutput, CopyFromParent,
00131 CWOverrideRedirect, &attribs);
00132 rect.x = 0;
00133 rect.y = 0;
00134 rect.width = s->width;
00135 rect.height = s->height;
00136 XShapeCombineRectangles(dpy, s->region_win,
00137 ShapeBounding, REGION_WIN_BORDER, REGION_WIN_BORDER,
00138 &rect, 1, ShapeSubtract, 0);
00139 XMapWindow(dpy, s->region_win);
00140 XSelectInput(dpy, s->region_win, ExposureMask | StructureNotifyMask);
00141 x11grab_draw_region_win(s);
00142 }
00143
00154 static int
00155 x11grab_read_header(AVFormatContext *s1)
00156 {
00157 struct x11_grab *x11grab = s1->priv_data;
00158 Display *dpy;
00159 AVStream *st = NULL;
00160 enum PixelFormat input_pixfmt;
00161 XImage *image;
00162 int x_off = 0;
00163 int y_off = 0;
00164 int screen;
00165 int use_shm;
00166 char *dpyname, *offset;
00167 int ret = 0;
00168 AVRational framerate;
00169
00170 dpyname = av_strdup(s1->filename);
00171 if (!dpyname)
00172 goto out;
00173
00174 offset = strchr(dpyname, '+');
00175 if (offset) {
00176 sscanf(offset, "%d,%d", &x_off, &y_off);
00177 x11grab->draw_mouse = !strstr(offset, "nomouse");
00178 *offset= 0;
00179 }
00180
00181 if ((ret = av_parse_video_rate(&framerate, x11grab->framerate)) < 0) {
00182 av_log(s1, AV_LOG_ERROR, "Could not parse framerate: %s.\n", x11grab->framerate);
00183 goto out;
00184 }
00185 av_log(s1, AV_LOG_INFO, "device: %s -> display: %s x: %d y: %d width: %d height: %d\n",
00186 s1->filename, dpyname, x_off, y_off, x11grab->width, x11grab->height);
00187
00188 dpy = XOpenDisplay(dpyname);
00189 av_freep(&dpyname);
00190 if(!dpy) {
00191 av_log(s1, AV_LOG_ERROR, "Could not open X display.\n");
00192 ret = AVERROR(EIO);
00193 goto out;
00194 }
00195
00196 st = avformat_new_stream(s1, NULL);
00197 if (!st) {
00198 ret = AVERROR(ENOMEM);
00199 goto out;
00200 }
00201 avpriv_set_pts_info(st, 64, 1, 1000000);
00202
00203 screen = DefaultScreen(dpy);
00204
00205 if (x11grab->follow_mouse) {
00206 int screen_w, screen_h;
00207 Window w;
00208
00209 screen_w = DisplayWidth(dpy, screen);
00210 screen_h = DisplayHeight(dpy, screen);
00211 XQueryPointer(dpy, RootWindow(dpy, screen), &w, &w, &x_off, &y_off, &ret, &ret, &ret);
00212 x_off -= x11grab->width / 2;
00213 y_off -= x11grab->height / 2;
00214 x_off = FFMIN(FFMAX(x_off, 0), screen_w - x11grab->width);
00215 y_off = FFMIN(FFMAX(y_off, 0), screen_h - x11grab->height);
00216 av_log(s1, AV_LOG_INFO, "followmouse is enabled, resetting grabbing region to x: %d y: %d\n", x_off, y_off);
00217 }
00218
00219 use_shm = XShmQueryExtension(dpy);
00220 av_log(s1, AV_LOG_INFO, "shared memory extension%s found\n", use_shm ? "" : " not");
00221
00222 if(use_shm) {
00223 int scr = XDefaultScreen(dpy);
00224 image = XShmCreateImage(dpy,
00225 DefaultVisual(dpy, scr),
00226 DefaultDepth(dpy, scr),
00227 ZPixmap,
00228 NULL,
00229 &x11grab->shminfo,
00230 x11grab->width, x11grab->height);
00231 x11grab->shminfo.shmid = shmget(IPC_PRIVATE,
00232 image->bytes_per_line * image->height,
00233 IPC_CREAT|0777);
00234 if (x11grab->shminfo.shmid == -1) {
00235 av_log(s1, AV_LOG_ERROR, "Fatal: Can't get shared memory!\n");
00236 ret = AVERROR(ENOMEM);
00237 goto out;
00238 }
00239 x11grab->shminfo.shmaddr = image->data = shmat(x11grab->shminfo.shmid, 0, 0);
00240 x11grab->shminfo.readOnly = False;
00241
00242 if (!XShmAttach(dpy, &x11grab->shminfo)) {
00243 av_log(s1, AV_LOG_ERROR, "Fatal: Failed to attach shared memory!\n");
00244
00245 ret = AVERROR(EIO);
00246 goto out;
00247 }
00248 } else {
00249 image = XGetImage(dpy, RootWindow(dpy, screen),
00250 x_off,y_off,
00251 x11grab->width, x11grab->height,
00252 AllPlanes, ZPixmap);
00253 }
00254
00255 switch (image->bits_per_pixel) {
00256 case 8:
00257 av_log (s1, AV_LOG_DEBUG, "8 bit palette\n");
00258 input_pixfmt = PIX_FMT_PAL8;
00259 break;
00260 case 16:
00261 if ( image->red_mask == 0xf800 &&
00262 image->green_mask == 0x07e0 &&
00263 image->blue_mask == 0x001f ) {
00264 av_log (s1, AV_LOG_DEBUG, "16 bit RGB565\n");
00265 input_pixfmt = PIX_FMT_RGB565;
00266 } else if (image->red_mask == 0x7c00 &&
00267 image->green_mask == 0x03e0 &&
00268 image->blue_mask == 0x001f ) {
00269 av_log(s1, AV_LOG_DEBUG, "16 bit RGB555\n");
00270 input_pixfmt = PIX_FMT_RGB555;
00271 } else {
00272 av_log(s1, AV_LOG_ERROR, "RGB ordering at image depth %i not supported ... aborting\n", image->bits_per_pixel);
00273 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);
00274 ret = AVERROR(EIO);
00275 goto out;
00276 }
00277 break;
00278 case 24:
00279 if ( image->red_mask == 0xff0000 &&
00280 image->green_mask == 0x00ff00 &&
00281 image->blue_mask == 0x0000ff ) {
00282 input_pixfmt = PIX_FMT_BGR24;
00283 } else if ( image->red_mask == 0x0000ff &&
00284 image->green_mask == 0x00ff00 &&
00285 image->blue_mask == 0xff0000 ) {
00286 input_pixfmt = PIX_FMT_RGB24;
00287 } else {
00288 av_log(s1, AV_LOG_ERROR,"rgb ordering at image depth %i not supported ... aborting\n", image->bits_per_pixel);
00289 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);
00290 ret = AVERROR(EIO);
00291 goto out;
00292 }
00293 break;
00294 case 32:
00295 input_pixfmt = PIX_FMT_0RGB32;
00296 break;
00297 default:
00298 av_log(s1, AV_LOG_ERROR, "image depth %i not supported ... aborting\n", image->bits_per_pixel);
00299 ret = AVERROR(EINVAL);
00300 goto out;
00301 }
00302
00303 x11grab->frame_size = x11grab->width * x11grab->height * image->bits_per_pixel/8;
00304 x11grab->dpy = dpy;
00305 x11grab->time_base = (AVRational){framerate.den, framerate.num};
00306 x11grab->time_frame = av_gettime() / av_q2d(x11grab->time_base);
00307 x11grab->x_off = x_off;
00308 x11grab->y_off = y_off;
00309 x11grab->image = image;
00310 x11grab->use_shm = use_shm;
00311
00312 st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
00313 st->codec->codec_id = CODEC_ID_RAWVIDEO;
00314 st->codec->width = x11grab->width;
00315 st->codec->height = x11grab->height;
00316 st->codec->pix_fmt = input_pixfmt;
00317 st->codec->time_base = x11grab->time_base;
00318 st->codec->bit_rate = x11grab->frame_size * 1/av_q2d(x11grab->time_base) * 8;
00319
00320 out:
00321 av_free(dpyname);
00322 return ret;
00323 }
00324
00332 static void
00333 paint_mouse_pointer(XImage *image, struct x11_grab *s)
00334 {
00335 int x_off = s->x_off;
00336 int y_off = s->y_off;
00337 int width = s->width;
00338 int height = s->height;
00339 Display *dpy = s->dpy;
00340 XFixesCursorImage *xcim;
00341 int x, y;
00342 int line, column;
00343 int to_line, to_column;
00344 int pixstride = image->bits_per_pixel >> 3;
00345
00346
00347
00348
00349 uint8_t *pix = image->data;
00350
00351
00352 if (image->bits_per_pixel != 24 && image->bits_per_pixel != 32)
00353 return;
00354
00355 xcim = XFixesGetCursorImage(dpy);
00356
00357 x = xcim->x - xcim->xhot;
00358 y = xcim->y - xcim->yhot;
00359
00360 to_line = FFMIN((y + xcim->height), (height + y_off));
00361 to_column = FFMIN((x + xcim->width), (width + x_off));
00362
00363 for (line = FFMAX(y, y_off); line < to_line; line++) {
00364 for (column = FFMAX(x, x_off); column < to_column; column++) {
00365 int xcim_addr = (line - y) * xcim->width + column - x;
00366 int image_addr = ((line - y_off) * width + column - x_off) * pixstride;
00367 int r = (uint8_t)(xcim->pixels[xcim_addr] >> 0);
00368 int g = (uint8_t)(xcim->pixels[xcim_addr] >> 8);
00369 int b = (uint8_t)(xcim->pixels[xcim_addr] >> 16);
00370 int a = (uint8_t)(xcim->pixels[xcim_addr] >> 24);
00371
00372 if (a == 255) {
00373 pix[image_addr+0] = r;
00374 pix[image_addr+1] = g;
00375 pix[image_addr+2] = b;
00376 } else if (a) {
00377
00378 pix[image_addr+0] = r + (pix[image_addr+0]*(255-a) + 255/2) / 255;
00379 pix[image_addr+1] = g + (pix[image_addr+1]*(255-a) + 255/2) / 255;
00380 pix[image_addr+2] = b + (pix[image_addr+2]*(255-a) + 255/2) / 255;
00381 }
00382 }
00383 }
00384
00385 XFree(xcim);
00386 xcim = NULL;
00387 }
00388
00389
00400 static int
00401 xget_zpixmap(Display *dpy, Drawable d, XImage *image, int x, int y)
00402 {
00403 xGetImageReply rep;
00404 xGetImageReq *req;
00405 long nbytes;
00406
00407 if (!image) {
00408 return 0;
00409 }
00410
00411 LockDisplay(dpy);
00412 GetReq(GetImage, req);
00413
00414
00415 req->drawable = d;
00416 req->x = x;
00417 req->y = y;
00418 req->width = image->width;
00419 req->height = image->height;
00420 req->planeMask = (unsigned int)AllPlanes;
00421 req->format = ZPixmap;
00422
00423 if (!_XReply(dpy, (xReply *)&rep, 0, xFalse) || !rep.length) {
00424 UnlockDisplay(dpy);
00425 SyncHandle();
00426 return 0;
00427 }
00428
00429 nbytes = (long)rep.length << 2;
00430 _XReadPad(dpy, image->data, nbytes);
00431
00432 UnlockDisplay(dpy);
00433 SyncHandle();
00434 return 1;
00435 }
00436
00444 static int
00445 x11grab_read_packet(AVFormatContext *s1, AVPacket *pkt)
00446 {
00447 struct x11_grab *s = s1->priv_data;
00448 Display *dpy = s->dpy;
00449 XImage *image = s->image;
00450 int x_off = s->x_off;
00451 int y_off = s->y_off;
00452
00453 int screen;
00454 Window root;
00455 int follow_mouse = s->follow_mouse;
00456
00457 int64_t curtime, delay;
00458 struct timespec ts;
00459
00460
00461 s->time_frame += INT64_C(1000000);
00462
00463
00464 for(;;) {
00465 curtime = av_gettime();
00466 delay = s->time_frame * av_q2d(s->time_base) - curtime;
00467 if (delay <= 0) {
00468 if (delay < INT64_C(-1000000) * av_q2d(s->time_base)) {
00469 s->time_frame += INT64_C(1000000);
00470 }
00471 break;
00472 }
00473 ts.tv_sec = delay / 1000000;
00474 ts.tv_nsec = (delay % 1000000) * 1000;
00475 nanosleep(&ts, NULL);
00476 }
00477
00478 av_init_packet(pkt);
00479 pkt->data = image->data;
00480 pkt->size = s->frame_size;
00481 pkt->pts = curtime;
00482
00483 screen = DefaultScreen(dpy);
00484 root = RootWindow(dpy, screen);
00485 if (follow_mouse) {
00486 int screen_w, screen_h;
00487 int pointer_x, pointer_y, _;
00488 Window w;
00489
00490 screen_w = DisplayWidth(dpy, screen);
00491 screen_h = DisplayHeight(dpy, screen);
00492 XQueryPointer(dpy, root, &w, &w, &pointer_x, &pointer_y, &_, &_, &_);
00493 if (follow_mouse == -1) {
00494
00495 x_off += pointer_x - s->width / 2 - x_off;
00496 y_off += pointer_y - s->height / 2 - y_off;
00497 } else {
00498
00499
00500 if (pointer_x > x_off + s->width - follow_mouse) {
00501 x_off += pointer_x - (x_off + s->width - follow_mouse);
00502 } else if (pointer_x < x_off + follow_mouse)
00503 x_off -= (x_off + follow_mouse) - pointer_x;
00504 if (pointer_y > y_off + s->height - follow_mouse) {
00505 y_off += pointer_y - (y_off + s->height - follow_mouse);
00506 } else if (pointer_y < y_off + follow_mouse)
00507 y_off -= (y_off + follow_mouse) - pointer_y;
00508 }
00509
00510 s->x_off = x_off = FFMIN(FFMAX(x_off, 0), screen_w - s->width);
00511 s->y_off = y_off = FFMIN(FFMAX(y_off, 0), screen_h - s->height);
00512
00513 if (s->show_region && s->region_win)
00514 XMoveWindow(dpy, s->region_win,
00515 s->x_off - REGION_WIN_BORDER,
00516 s->y_off - REGION_WIN_BORDER);
00517 }
00518
00519 if (s->show_region) {
00520 if (s->region_win) {
00521 XEvent evt;
00522
00523 for (evt.type = NoEventMask; XCheckMaskEvent(dpy, ExposureMask | StructureNotifyMask, &evt); );
00524 if (evt.type)
00525 x11grab_draw_region_win(s);
00526 } else {
00527 x11grab_region_win_init(s);
00528 }
00529 }
00530
00531 if(s->use_shm) {
00532 if (!XShmGetImage(dpy, root, image, x_off, y_off, AllPlanes)) {
00533 av_log (s1, AV_LOG_INFO, "XShmGetImage() failed\n");
00534 }
00535 } else {
00536 if (!xget_zpixmap(dpy, root, image, x_off, y_off)) {
00537 av_log (s1, AV_LOG_INFO, "XGetZPixmap() failed\n");
00538 }
00539 }
00540
00541 if (s->draw_mouse) {
00542 paint_mouse_pointer(image, s);
00543 }
00544
00545 return s->frame_size;
00546 }
00547
00554 static int
00555 x11grab_read_close(AVFormatContext *s1)
00556 {
00557 struct x11_grab *x11grab = s1->priv_data;
00558
00559
00560 if (x11grab->use_shm) {
00561 XShmDetach(x11grab->dpy, &x11grab->shminfo);
00562 shmdt(x11grab->shminfo.shmaddr);
00563 shmctl(x11grab->shminfo.shmid, IPC_RMID, NULL);
00564 }
00565
00566
00567 if (x11grab->image) {
00568 XDestroyImage(x11grab->image);
00569 x11grab->image = NULL;
00570 }
00571
00572 if (x11grab->region_win) {
00573 XDestroyWindow(x11grab->dpy, x11grab->region_win);
00574 }
00575
00576
00577 XCloseDisplay(x11grab->dpy);
00578 return 0;
00579 }
00580
00581 #define OFFSET(x) offsetof(struct x11_grab, x)
00582 #define DEC AV_OPT_FLAG_DECODING_PARAM
00583 static const AVOption options[] = {
00584 { "video_size", "A string describing frame size, such as 640x480 or hd720.", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, {.str = "vga"}, 0, 0, DEC },
00585 { "framerate", "", OFFSET(framerate), AV_OPT_TYPE_STRING, {.str = "ntsc"}, 0, 0, DEC },
00586 { "draw_mouse", "Draw the mouse pointer.", OFFSET(draw_mouse), AV_OPT_TYPE_INT, { 1 }, 0, 1, DEC },
00587 { "follow_mouse", "Move the grabbing region when the mouse pointer reaches within specified amount of pixels to the edge of region.",
00588 OFFSET(follow_mouse), AV_OPT_TYPE_INT, { 0 }, -1, INT_MAX, DEC, "follow_mouse" },
00589 { "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" },
00590 { "show_region", "Show the grabbing region.", OFFSET(show_region), AV_OPT_TYPE_INT, { 0 }, 0, 1, DEC },
00591 { NULL },
00592 };
00593
00594 static const AVClass x11_class = {
00595 .class_name = "X11grab indev",
00596 .item_name = av_default_item_name,
00597 .option = options,
00598 .version = LIBAVUTIL_VERSION_INT,
00599 };
00600
00602 AVInputFormat ff_x11_grab_device_demuxer = {
00603 .name = "x11grab",
00604 .long_name = NULL_IF_CONFIG_SMALL("X11grab"),
00605 .priv_data_size = sizeof(struct x11_grab),
00606 .read_header = x11grab_read_header,
00607 .read_packet = x11grab_read_packet,
00608 .read_close = x11grab_read_close,
00609 .flags = AVFMT_NOFILE,
00610 .priv_class = &x11_class,
00611 };