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