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