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 "libavutil/time.h"
00045 #include <time.h>
00046 #include <X11/cursorfont.h>
00047 #include <X11/X.h>
00048 #include <X11/Xlib.h>
00049 #include <X11/Xlibint.h>
00050 #include <X11/Xproto.h>
00051 #include <X11/Xutil.h>
00052 #include <sys/shm.h>
00053 #include <X11/extensions/shape.h>
00054 #include <X11/extensions/XShm.h>
00055 #include <X11/extensions/Xfixes.h>
00056 #include "avdevice.h"
00057
00061 struct x11grab {
00062 const AVClass *class;
00063 int frame_size;
00064 AVRational time_base;
00065 int64_t time_frame;
00067 int width;
00068 int height;
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 x11grab *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 x11grab *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
00155 static int
00156 x11grab_read_header(AVFormatContext *s1)
00157 {
00158 struct x11grab *x11grab = s1->priv_data;
00159 Display *dpy;
00160 AVStream *st = NULL;
00161 enum AVPixelFormat input_pixfmt;
00162 XImage *image;
00163 int x_off = 0;
00164 int y_off = 0;
00165 int screen;
00166 int use_shm;
00167 char *dpyname, *offset;
00168 int ret = 0;
00169 AVRational framerate;
00170
00171 dpyname = av_strdup(s1->filename);
00172 if (!dpyname)
00173 goto out;
00174
00175 offset = strchr(dpyname, '+');
00176 if (offset) {
00177 sscanf(offset, "%d,%d", &x_off, &y_off);
00178 if (strstr(offset, "nomouse")) {
00179 av_log(s1, AV_LOG_WARNING,
00180 "'nomouse' specification in argument is deprecated: "
00181 "use 'draw_mouse' option with value 0 instead\n");
00182 x11grab->draw_mouse = 0;
00183 }
00184 *offset= 0;
00185 }
00186
00187 if ((ret = av_parse_video_rate(&framerate, x11grab->framerate)) < 0) {
00188 av_log(s1, AV_LOG_ERROR, "Could not parse framerate: %s.\n", x11grab->framerate);
00189 goto out;
00190 }
00191 av_log(s1, AV_LOG_INFO, "device: %s -> display: %s x: %d y: %d width: %d height: %d\n",
00192 s1->filename, dpyname, x_off, y_off, x11grab->width, x11grab->height);
00193
00194 dpy = XOpenDisplay(dpyname);
00195 av_freep(&dpyname);
00196 if(!dpy) {
00197 av_log(s1, AV_LOG_ERROR, "Could not open X display.\n");
00198 ret = AVERROR(EIO);
00199 goto out;
00200 }
00201
00202 st = avformat_new_stream(s1, NULL);
00203 if (!st) {
00204 ret = AVERROR(ENOMEM);
00205 goto out;
00206 }
00207 avpriv_set_pts_info(st, 64, 1, 1000000);
00208
00209 screen = DefaultScreen(dpy);
00210
00211 if (x11grab->follow_mouse) {
00212 int screen_w, screen_h;
00213 Window w;
00214
00215 screen_w = DisplayWidth(dpy, screen);
00216 screen_h = DisplayHeight(dpy, screen);
00217 XQueryPointer(dpy, RootWindow(dpy, screen), &w, &w, &x_off, &y_off, &ret, &ret, &ret);
00218 x_off -= x11grab->width / 2;
00219 y_off -= x11grab->height / 2;
00220 x_off = FFMIN(FFMAX(x_off, 0), screen_w - x11grab->width);
00221 y_off = FFMIN(FFMAX(y_off, 0), screen_h - x11grab->height);
00222 av_log(s1, AV_LOG_INFO, "followmouse is enabled, resetting grabbing region to x: %d y: %d\n", x_off, y_off);
00223 }
00224
00225 use_shm = XShmQueryExtension(dpy);
00226 av_log(s1, AV_LOG_INFO, "shared memory extension%s found\n", use_shm ? "" : " not");
00227
00228 if(use_shm) {
00229 int scr = XDefaultScreen(dpy);
00230 image = XShmCreateImage(dpy,
00231 DefaultVisual(dpy, scr),
00232 DefaultDepth(dpy, scr),
00233 ZPixmap,
00234 NULL,
00235 &x11grab->shminfo,
00236 x11grab->width, x11grab->height);
00237 x11grab->shminfo.shmid = shmget(IPC_PRIVATE,
00238 image->bytes_per_line * image->height,
00239 IPC_CREAT|0777);
00240 if (x11grab->shminfo.shmid == -1) {
00241 av_log(s1, AV_LOG_ERROR, "Fatal: Can't get shared memory!\n");
00242 ret = AVERROR(ENOMEM);
00243 goto out;
00244 }
00245 x11grab->shminfo.shmaddr = image->data = shmat(x11grab->shminfo.shmid, 0, 0);
00246 x11grab->shminfo.readOnly = False;
00247
00248 if (!XShmAttach(dpy, &x11grab->shminfo)) {
00249 av_log(s1, AV_LOG_ERROR, "Fatal: Failed to attach shared memory!\n");
00250
00251 ret = AVERROR(EIO);
00252 goto out;
00253 }
00254 } else {
00255 image = XGetImage(dpy, RootWindow(dpy, screen),
00256 x_off,y_off,
00257 x11grab->width, x11grab->height,
00258 AllPlanes, ZPixmap);
00259 }
00260
00261 switch (image->bits_per_pixel) {
00262 case 8:
00263 av_log (s1, AV_LOG_DEBUG, "8 bit palette\n");
00264 input_pixfmt = AV_PIX_FMT_PAL8;
00265 break;
00266 case 16:
00267 if ( image->red_mask == 0xf800 &&
00268 image->green_mask == 0x07e0 &&
00269 image->blue_mask == 0x001f ) {
00270 av_log (s1, AV_LOG_DEBUG, "16 bit RGB565\n");
00271 input_pixfmt = AV_PIX_FMT_RGB565;
00272 } else if (image->red_mask == 0x7c00 &&
00273 image->green_mask == 0x03e0 &&
00274 image->blue_mask == 0x001f ) {
00275 av_log(s1, AV_LOG_DEBUG, "16 bit RGB555\n");
00276 input_pixfmt = AV_PIX_FMT_RGB555;
00277 } else {
00278 av_log(s1, AV_LOG_ERROR, "RGB ordering at image depth %i not supported ... aborting\n", image->bits_per_pixel);
00279 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);
00280 ret = AVERROR(EIO);
00281 goto out;
00282 }
00283 break;
00284 case 24:
00285 if ( image->red_mask == 0xff0000 &&
00286 image->green_mask == 0x00ff00 &&
00287 image->blue_mask == 0x0000ff ) {
00288 input_pixfmt = AV_PIX_FMT_BGR24;
00289 } else if ( image->red_mask == 0x0000ff &&
00290 image->green_mask == 0x00ff00 &&
00291 image->blue_mask == 0xff0000 ) {
00292 input_pixfmt = AV_PIX_FMT_RGB24;
00293 } else {
00294 av_log(s1, AV_LOG_ERROR,"rgb ordering at image depth %i not supported ... aborting\n", image->bits_per_pixel);
00295 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);
00296 ret = AVERROR(EIO);
00297 goto out;
00298 }
00299 break;
00300 case 32:
00301 input_pixfmt = AV_PIX_FMT_0RGB32;
00302 break;
00303 default:
00304 av_log(s1, AV_LOG_ERROR, "image depth %i not supported ... aborting\n", image->bits_per_pixel);
00305 ret = AVERROR(EINVAL);
00306 goto out;
00307 }
00308
00309 x11grab->frame_size = x11grab->width * x11grab->height * image->bits_per_pixel/8;
00310 x11grab->dpy = dpy;
00311 x11grab->time_base = av_inv_q(framerate);
00312 x11grab->time_frame = av_gettime() / av_q2d(x11grab->time_base);
00313 x11grab->x_off = x_off;
00314 x11grab->y_off = y_off;
00315 x11grab->image = image;
00316 x11grab->use_shm = use_shm;
00317
00318 st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
00319 st->codec->codec_id = AV_CODEC_ID_RAWVIDEO;
00320 st->codec->width = x11grab->width;
00321 st->codec->height = x11grab->height;
00322 st->codec->pix_fmt = input_pixfmt;
00323 st->codec->time_base = x11grab->time_base;
00324 st->codec->bit_rate = x11grab->frame_size * 1/av_q2d(x11grab->time_base) * 8;
00325
00326 out:
00327 av_free(dpyname);
00328 return ret;
00329 }
00330
00338 static void
00339 paint_mouse_pointer(XImage *image, struct x11grab *s)
00340 {
00341 int x_off = s->x_off;
00342 int y_off = s->y_off;
00343 int width = s->width;
00344 int height = s->height;
00345 Display *dpy = s->dpy;
00346 XFixesCursorImage *xcim;
00347 int x, y;
00348 int line, column;
00349 int to_line, to_column;
00350 int pixstride = image->bits_per_pixel >> 3;
00351
00352
00353
00354
00355 uint8_t *pix = image->data;
00356 Cursor c;
00357 Window w;
00358 XSetWindowAttributes attr;
00359
00360
00361 if (image->bits_per_pixel != 24 && image->bits_per_pixel != 32)
00362 return;
00363
00364 c = XCreateFontCursor(dpy, XC_left_ptr);
00365 w = DefaultRootWindow(dpy);
00366 attr.cursor = c;
00367 XChangeWindowAttributes(dpy, w, CWCursor, &attr);
00368
00369 xcim = XFixesGetCursorImage(dpy);
00370
00371 x = xcim->x - xcim->xhot;
00372 y = xcim->y - xcim->yhot;
00373
00374 to_line = FFMIN((y + xcim->height), (height + y_off));
00375 to_column = FFMIN((x + xcim->width), (width + x_off));
00376
00377 for (line = FFMAX(y, y_off); line < to_line; line++) {
00378 for (column = FFMAX(x, x_off); column < to_column; column++) {
00379 int xcim_addr = (line - y) * xcim->width + column - x;
00380 int image_addr = ((line - y_off) * width + column - x_off) * pixstride;
00381 int r = (uint8_t)(xcim->pixels[xcim_addr] >> 0);
00382 int g = (uint8_t)(xcim->pixels[xcim_addr] >> 8);
00383 int b = (uint8_t)(xcim->pixels[xcim_addr] >> 16);
00384 int a = (uint8_t)(xcim->pixels[xcim_addr] >> 24);
00385
00386 if (a == 255) {
00387 pix[image_addr+0] = r;
00388 pix[image_addr+1] = g;
00389 pix[image_addr+2] = b;
00390 } else if (a) {
00391
00392 pix[image_addr+0] = r + (pix[image_addr+0]*(255-a) + 255/2) / 255;
00393 pix[image_addr+1] = g + (pix[image_addr+1]*(255-a) + 255/2) / 255;
00394 pix[image_addr+2] = b + (pix[image_addr+2]*(255-a) + 255/2) / 255;
00395 }
00396 }
00397 }
00398
00399 XFree(xcim);
00400 xcim = NULL;
00401 }
00402
00403
00414 static int
00415 xget_zpixmap(Display *dpy, Drawable d, XImage *image, int x, int y)
00416 {
00417 xGetImageReply rep;
00418 xGetImageReq *req;
00419 long nbytes;
00420
00421 if (!image) {
00422 return 0;
00423 }
00424
00425 LockDisplay(dpy);
00426 GetReq(GetImage, req);
00427
00428
00429 req->drawable = d;
00430 req->x = x;
00431 req->y = y;
00432 req->width = image->width;
00433 req->height = image->height;
00434 req->planeMask = (unsigned int)AllPlanes;
00435 req->format = ZPixmap;
00436
00437 if (!_XReply(dpy, (xReply *)&rep, 0, xFalse) || !rep.length) {
00438 UnlockDisplay(dpy);
00439 SyncHandle();
00440 return 0;
00441 }
00442
00443 nbytes = (long)rep.length << 2;
00444 _XReadPad(dpy, image->data, nbytes);
00445
00446 UnlockDisplay(dpy);
00447 SyncHandle();
00448 return 1;
00449 }
00450
00458 static int
00459 x11grab_read_packet(AVFormatContext *s1, AVPacket *pkt)
00460 {
00461 struct x11grab *s = s1->priv_data;
00462 Display *dpy = s->dpy;
00463 XImage *image = s->image;
00464 int x_off = s->x_off;
00465 int y_off = s->y_off;
00466
00467 int screen;
00468 Window root;
00469 int follow_mouse = s->follow_mouse;
00470
00471 int64_t curtime, delay;
00472 struct timespec ts;
00473
00474
00475 s->time_frame += INT64_C(1000000);
00476
00477
00478 for(;;) {
00479 curtime = av_gettime();
00480 delay = s->time_frame * av_q2d(s->time_base) - curtime;
00481 if (delay <= 0) {
00482 if (delay < INT64_C(-1000000) * av_q2d(s->time_base)) {
00483 s->time_frame += INT64_C(1000000);
00484 }
00485 break;
00486 }
00487 ts.tv_sec = delay / 1000000;
00488 ts.tv_nsec = (delay % 1000000) * 1000;
00489 nanosleep(&ts, NULL);
00490 }
00491
00492 av_init_packet(pkt);
00493 pkt->data = image->data;
00494 pkt->size = s->frame_size;
00495 pkt->pts = curtime;
00496
00497 screen = DefaultScreen(dpy);
00498 root = RootWindow(dpy, screen);
00499 if (follow_mouse) {
00500 int screen_w, screen_h;
00501 int pointer_x, pointer_y, _;
00502 Window w;
00503
00504 screen_w = DisplayWidth(dpy, screen);
00505 screen_h = DisplayHeight(dpy, screen);
00506 XQueryPointer(dpy, root, &w, &w, &pointer_x, &pointer_y, &_, &_, &_);
00507 if (follow_mouse == -1) {
00508
00509 x_off += pointer_x - s->width / 2 - x_off;
00510 y_off += pointer_y - s->height / 2 - y_off;
00511 } else {
00512
00513
00514 if (pointer_x > x_off + s->width - follow_mouse) {
00515 x_off += pointer_x - (x_off + s->width - follow_mouse);
00516 } else if (pointer_x < x_off + follow_mouse)
00517 x_off -= (x_off + follow_mouse) - pointer_x;
00518 if (pointer_y > y_off + s->height - follow_mouse) {
00519 y_off += pointer_y - (y_off + s->height - follow_mouse);
00520 } else if (pointer_y < y_off + follow_mouse)
00521 y_off -= (y_off + follow_mouse) - pointer_y;
00522 }
00523
00524 s->x_off = x_off = FFMIN(FFMAX(x_off, 0), screen_w - s->width);
00525 s->y_off = y_off = FFMIN(FFMAX(y_off, 0), screen_h - s->height);
00526
00527 if (s->show_region && s->region_win)
00528 XMoveWindow(dpy, s->region_win,
00529 s->x_off - REGION_WIN_BORDER,
00530 s->y_off - REGION_WIN_BORDER);
00531 }
00532
00533 if (s->show_region) {
00534 if (s->region_win) {
00535 XEvent evt;
00536
00537 for (evt.type = NoEventMask; XCheckMaskEvent(dpy, ExposureMask | StructureNotifyMask, &evt); );
00538 if (evt.type)
00539 x11grab_draw_region_win(s);
00540 } else {
00541 x11grab_region_win_init(s);
00542 }
00543 }
00544
00545 if(s->use_shm) {
00546 if (!XShmGetImage(dpy, root, image, x_off, y_off, AllPlanes)) {
00547 av_log (s1, AV_LOG_INFO, "XShmGetImage() failed\n");
00548 }
00549 } else {
00550 if (!xget_zpixmap(dpy, root, image, x_off, y_off)) {
00551 av_log (s1, AV_LOG_INFO, "XGetZPixmap() failed\n");
00552 }
00553 }
00554
00555 if (s->draw_mouse) {
00556 paint_mouse_pointer(image, s);
00557 }
00558
00559 return s->frame_size;
00560 }
00561
00568 static int
00569 x11grab_read_close(AVFormatContext *s1)
00570 {
00571 struct x11grab *x11grab = s1->priv_data;
00572
00573
00574 if (x11grab->use_shm) {
00575 XShmDetach(x11grab->dpy, &x11grab->shminfo);
00576 shmdt(x11grab->shminfo.shmaddr);
00577 shmctl(x11grab->shminfo.shmid, IPC_RMID, NULL);
00578 }
00579
00580
00581 if (x11grab->image) {
00582 XDestroyImage(x11grab->image);
00583 x11grab->image = NULL;
00584 }
00585
00586 if (x11grab->region_win) {
00587 XDestroyWindow(x11grab->dpy, x11grab->region_win);
00588 }
00589
00590
00591 XCloseDisplay(x11grab->dpy);
00592 return 0;
00593 }
00594
00595 #define OFFSET(x) offsetof(struct x11grab, x)
00596 #define DEC AV_OPT_FLAG_DECODING_PARAM
00597 static const AVOption options[] = {
00598 { "draw_mouse", "draw the mouse pointer", OFFSET(draw_mouse), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, DEC },
00599
00600 { "follow_mouse", "move the grabbing region when the mouse pointer reaches within specified amount of pixels to the edge of region",
00601 OFFSET(follow_mouse), AV_OPT_TYPE_INT, {.i64 = 0}, -1, INT_MAX, DEC, "follow_mouse" },
00602 { "centered", "keep the mouse pointer at the center of grabbing region when following",
00603 0, AV_OPT_TYPE_CONST, {.i64 = -1}, INT_MIN, INT_MAX, DEC, "follow_mouse" },
00604
00605 { "framerate", "set video frame rate", OFFSET(framerate), AV_OPT_TYPE_STRING, {.str = "ntsc"}, 0, 0, DEC },
00606 { "show_region", "show the grabbing region", OFFSET(show_region), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, DEC },
00607 { "video_size", "set video frame size", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, {.str = "vga"}, 0, 0, DEC },
00608 { NULL },
00609 };
00610
00611 static const AVClass x11_class = {
00612 .class_name = "X11grab indev",
00613 .item_name = av_default_item_name,
00614 .option = options,
00615 .version = LIBAVUTIL_VERSION_INT,
00616 };
00617
00619 AVInputFormat ff_x11grab_demuxer = {
00620 .name = "x11grab",
00621 .long_name = NULL_IF_CONFIG_SMALL("X11grab"),
00622 .priv_data_size = sizeof(struct x11grab),
00623 .read_header = x11grab_read_header,
00624 .read_packet = x11grab_read_packet,
00625 .read_close = x11grab_read_close,
00626 .flags = AVFMT_NOFILE,
00627 .priv_class = &x11_class,
00628 };