1/* 2 * X11 video grab interface 3 * 4 * This file is part of Libav. 5 * 6 * Libav integration: 7 * Copyright (C) 2006 Clemens Fruhwirth <clemens@endorphin.org> 8 * Edouard Gomez <ed.gomez@free.fr> 9 * 10 * This file contains code from grab.c: 11 * Copyright (c) 2000-2001 Fabrice Bellard 12 * 13 * This file contains code from the xvidcap project: 14 * Copyright (C) 1997-1998 Rasca, Berlin 15 * 2003-2004 Karl H. Beckers, Frankfurt 16 * 17 * Libav is free software; you can redistribute it and/or modify 18 * it under the terms of the GNU General Public License as published by 19 * the Free Software Foundation; either version 2 of the License, or 20 * (at your option) any later version. 21 * 22 * Libav is distributed in the hope that it will be useful, 23 * but WITHOUT ANY WARRANTY; without even the implied warranty of 24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 * GNU General Public License for more details. 26 * 27 * You should have received a copy of the GNU General Public License 28 * along with Libav; if not, write to the Free Software 29 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 30 */ 31 32/** 33 * @file 34 * X11 frame device demuxer 35 * @author Clemens Fruhwirth <clemens@endorphin.org> 36 * @author Edouard Gomez <ed.gomez@free.fr> 37 */ 38 39#include "config.h" 40#include "libavformat/avformat.h" 41#include "libavformat/internal.h" 42#include "libavutil/log.h" 43#include "libavutil/opt.h" 44#include "libavutil/parseutils.h" 45#include <time.h> 46#include <X11/X.h> 47#include <X11/Xlib.h> 48#include <X11/Xlibint.h> 49#include <X11/Xproto.h> 50#include <X11/Xutil.h> 51#include <sys/shm.h> 52#include <X11/extensions/shape.h> 53#include <X11/extensions/XShm.h> 54#include <X11/extensions/Xfixes.h> 55 56/** 57 * X11 Device Demuxer context 58 */ 59struct x11_grab 60{ 61 const AVClass *class; /**< Class for private options. */ 62 int frame_size; /**< Size in bytes of a grabbed frame */ 63 AVRational time_base; /**< Time base */ 64 int64_t time_frame; /**< Current time */ 65 66 char *video_size; /**< String describing video size, set by a private option. */ 67 int height; /**< Height of the grab frame */ 68 int width; /**< Width of the grab frame */ 69 int x_off; /**< Horizontal top-left corner coordinate */ 70 int y_off; /**< Vertical top-left corner coordinate */ 71 72 Display *dpy; /**< X11 display from which x11grab grabs frames */ 73 XImage *image; /**< X11 image holding the grab */ 74 int use_shm; /**< !0 when using XShm extension */ 75 XShmSegmentInfo shminfo; /**< When using XShm, keeps track of XShm infos */ 76 int draw_mouse; /**< Set by a private option. */ 77 int follow_mouse; /**< Set by a private option. */ 78 int show_region; /**< set by a private option. */ 79 char *framerate; /**< Set by a private option. */ 80 81 Window region_win; /**< This is used by show_region option. */ 82}; 83 84#define REGION_WIN_BORDER 3 85/** 86 * Draw grabbing region window 87 * 88 * @param s x11_grab context 89 */ 90static void 91x11grab_draw_region_win(struct x11_grab *s) 92{ 93 Display *dpy = s->dpy; 94 int screen; 95 Window win = s->region_win; 96 GC gc; 97 98 screen = DefaultScreen(dpy); 99 gc = XCreateGC(dpy, win, 0, 0); 100 XSetForeground(dpy, gc, WhitePixel(dpy, screen)); 101 XSetBackground(dpy, gc, BlackPixel(dpy, screen)); 102 XSetLineAttributes(dpy, gc, REGION_WIN_BORDER, LineDoubleDash, 0, 0); 103 XDrawRectangle(dpy, win, gc, 104 1, 1, 105 (s->width + REGION_WIN_BORDER * 2) - 1 * 2 - 1, 106 (s->height + REGION_WIN_BORDER * 2) - 1 * 2 - 1); 107 XFreeGC(dpy, gc); 108} 109 110/** 111 * Initialize grabbing region window 112 * 113 * @param s x11_grab context 114 */ 115static void 116x11grab_region_win_init(struct x11_grab *s) 117{ 118 Display *dpy = s->dpy; 119 int screen; 120 XSetWindowAttributes attribs; 121 XRectangle rect; 122 123 screen = DefaultScreen(dpy); 124 attribs.override_redirect = True; 125 s->region_win = XCreateWindow(dpy, RootWindow(dpy, screen), 126 s->x_off - REGION_WIN_BORDER, 127 s->y_off - REGION_WIN_BORDER, 128 s->width + REGION_WIN_BORDER * 2, 129 s->height + REGION_WIN_BORDER * 2, 130 0, CopyFromParent, 131 InputOutput, CopyFromParent, 132 CWOverrideRedirect, &attribs); 133 rect.x = 0; 134 rect.y = 0; 135 rect.width = s->width; 136 rect.height = s->height; 137 XShapeCombineRectangles(dpy, s->region_win, 138 ShapeBounding, REGION_WIN_BORDER, REGION_WIN_BORDER, 139 &rect, 1, ShapeSubtract, 0); 140 XMapWindow(dpy, s->region_win); 141 XSelectInput(dpy, s->region_win, ExposureMask | StructureNotifyMask); 142 x11grab_draw_region_win(s); 143} 144 145/** 146 * Initialize the x11 grab device demuxer (public device demuxer API). 147 * 148 * @param s1 Context from avformat core 149 * @param ap Parameters from avformat core 150 * @return <ul> 151 * <li>AVERROR(ENOMEM) no memory left</li> 152 * <li>AVERROR(EIO) other failure case</li> 153 * <li>0 success</li> 154 * </ul> 155 */ 156static int 157x11grab_read_header(AVFormatContext *s1, AVFormatParameters *ap) 158{ 159 struct x11_grab *x11grab = s1->priv_data; 160 Display *dpy; 161 AVStream *st = NULL; 162 enum PixelFormat input_pixfmt; 163 XImage *image; 164 int x_off = 0; 165 int y_off = 0; 166 int screen; 167 int use_shm; 168 char *param, *offset; 169 int ret = 0; 170 AVRational framerate; 171 172 param = av_strdup(s1->filename); 173 offset = strchr(param, '+'); 174 if (offset) { 175 sscanf(offset, "%d,%d", &x_off, &y_off); 176 x11grab->draw_mouse = !strstr(offset, "nomouse"); 177 *offset= 0; 178 } 179 180 if ((ret = av_parse_video_size(&x11grab->width, &x11grab->height, x11grab->video_size)) < 0) { 181 av_log(s1, AV_LOG_ERROR, "Couldn't parse video size.\n"); 182 goto out; 183 } 184 if ((ret = av_parse_video_rate(&framerate, x11grab->framerate)) < 0) { 185 av_log(s1, AV_LOG_ERROR, "Could not parse framerate: %s.\n", x11grab->framerate); 186 goto out; 187 } 188 av_log(s1, AV_LOG_INFO, "device: %s -> display: %s x: %d y: %d width: %d height: %d\n", 189 s1->filename, param, x_off, y_off, x11grab->width, x11grab->height); 190 191 dpy = XOpenDisplay(param); 192 if(!dpy) { 193 av_log(s1, AV_LOG_ERROR, "Could not open X display.\n"); 194 ret = AVERROR(EIO); 195 goto out; 196 } 197 198 st = avformat_new_stream(s1, NULL); 199 if (!st) { 200 ret = AVERROR(ENOMEM); 201 goto out; 202 } 203 avpriv_set_pts_info(st, 64, 1, 1000000); /* 64 bits pts in us */ 204 205 screen = DefaultScreen(dpy); 206 207 if (x11grab->follow_mouse) { 208 int screen_w, screen_h; 209 Window w; 210 211 screen_w = DisplayWidth(dpy, screen); 212 screen_h = DisplayHeight(dpy, screen); 213 XQueryPointer(dpy, RootWindow(dpy, screen), &w, &w, &x_off, &y_off, &ret, &ret, &ret); 214 x_off -= x11grab->width / 2; 215 y_off -= x11grab->height / 2; 216 x_off = FFMIN(FFMAX(x_off, 0), screen_w - x11grab->width); 217 y_off = FFMIN(FFMAX(y_off, 0), screen_h - x11grab->height); 218 av_log(s1, AV_LOG_INFO, "followmouse is enabled, resetting grabbing region to x: %d y: %d\n", x_off, y_off); 219 } 220 221 use_shm = XShmQueryExtension(dpy); 222 av_log(s1, AV_LOG_INFO, "shared memory extension %s found\n", use_shm ? "" : "not"); 223 224 if(use_shm) { 225 int scr = XDefaultScreen(dpy); 226 image = XShmCreateImage(dpy, 227 DefaultVisual(dpy, scr), 228 DefaultDepth(dpy, scr), 229 ZPixmap, 230 NULL, 231 &x11grab->shminfo, 232 x11grab->width, x11grab->height); 233 x11grab->shminfo.shmid = shmget(IPC_PRIVATE, 234 image->bytes_per_line * image->height, 235 IPC_CREAT|0777); 236 if (x11grab->shminfo.shmid == -1) { 237 av_log(s1, AV_LOG_ERROR, "Fatal: Can't get shared memory!\n"); 238 ret = AVERROR(ENOMEM); 239 goto out; 240 } 241 x11grab->shminfo.shmaddr = image->data = shmat(x11grab->shminfo.shmid, 0, 0); 242 x11grab->shminfo.readOnly = False; 243 244 if (!XShmAttach(dpy, &x11grab->shminfo)) { 245 av_log(s1, AV_LOG_ERROR, "Fatal: Failed to attach shared memory!\n"); 246 /* needs some better error subroutine :) */ 247 ret = AVERROR(EIO); 248 goto out; 249 } 250 } else { 251 image = XGetImage(dpy, RootWindow(dpy, screen), 252 x_off,y_off, 253 x11grab->width, x11grab->height, 254 AllPlanes, ZPixmap); 255 } 256 257 switch (image->bits_per_pixel) { 258 case 8: 259 av_log (s1, AV_LOG_DEBUG, "8 bit palette\n"); 260 input_pixfmt = PIX_FMT_PAL8; 261 break; 262 case 16: 263 if ( image->red_mask == 0xf800 && 264 image->green_mask == 0x07e0 && 265 image->blue_mask == 0x001f ) { 266 av_log (s1, AV_LOG_DEBUG, "16 bit RGB565\n"); 267 input_pixfmt = PIX_FMT_RGB565; 268 } else if (image->red_mask == 0x7c00 && 269 image->green_mask == 0x03e0 && 270 image->blue_mask == 0x001f ) { 271 av_log(s1, AV_LOG_DEBUG, "16 bit RGB555\n"); 272 input_pixfmt = PIX_FMT_RGB555; 273 } else { 274 av_log(s1, AV_LOG_ERROR, "RGB ordering at image depth %i not supported ... aborting\n", image->bits_per_pixel); 275 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); 276 ret = AVERROR(EIO); 277 goto out; 278 } 279 break; 280 case 24: 281 if ( image->red_mask == 0xff0000 && 282 image->green_mask == 0x00ff00 && 283 image->blue_mask == 0x0000ff ) { 284 input_pixfmt = PIX_FMT_BGR24; 285 } else if ( image->red_mask == 0x0000ff && 286 image->green_mask == 0x00ff00 && 287 image->blue_mask == 0xff0000 ) { 288 input_pixfmt = PIX_FMT_RGB24; 289 } else { 290 av_log(s1, AV_LOG_ERROR,"rgb ordering at image depth %i not supported ... aborting\n", image->bits_per_pixel); 291 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); 292 ret = AVERROR(EIO); 293 goto out; 294 } 295 break; 296 case 32: 297 input_pixfmt = PIX_FMT_RGB32; 298 break; 299 default: 300 av_log(s1, AV_LOG_ERROR, "image depth %i not supported ... aborting\n", image->bits_per_pixel); 301 ret = AVERROR(EINVAL); 302 goto out; 303 } 304 305 x11grab->frame_size = x11grab->width * x11grab->height * image->bits_per_pixel/8; 306 x11grab->dpy = dpy; 307 x11grab->time_base = (AVRational){framerate.den, framerate.num}; 308 x11grab->time_frame = av_gettime() / av_q2d(x11grab->time_base); 309 x11grab->x_off = x_off; 310 x11grab->y_off = y_off; 311 x11grab->image = image; 312 x11grab->use_shm = use_shm; 313 314 st->codec->codec_type = AVMEDIA_TYPE_VIDEO; 315 st->codec->codec_id = CODEC_ID_RAWVIDEO; 316 st->codec->width = x11grab->width; 317 st->codec->height = x11grab->height; 318 st->codec->pix_fmt = input_pixfmt; 319 st->codec->time_base = x11grab->time_base; 320 st->codec->bit_rate = x11grab->frame_size * 1/av_q2d(x11grab->time_base) * 8; 321 322out: 323 return ret; 324} 325 326/** 327 * Paint a mouse pointer in an X11 image. 328 * 329 * @param image image to paint the mouse pointer to 330 * @param s context used to retrieve original grabbing rectangle 331 * coordinates 332 */ 333static void 334paint_mouse_pointer(XImage *image, struct x11_grab *s) 335{ 336 int x_off = s->x_off; 337 int y_off = s->y_off; 338 int width = s->width; 339 int height = s->height; 340 Display *dpy = s->dpy; 341 XFixesCursorImage *xcim; 342 int x, y; 343 int line, column; 344 int to_line, to_column; 345 int pixstride = image->bits_per_pixel >> 3; 346 /* Warning: in its insanity, xlib provides unsigned image data through a 347 * char* pointer, so we have to make it uint8_t to make things not break. 348 * Anyone who performs further investigation of the xlib API likely risks 349 * permanent brain damage. */ 350 uint8_t *pix = image->data; 351 352 /* Code doesn't currently support 16-bit or PAL8 */ 353 if (image->bits_per_pixel != 24 && image->bits_per_pixel != 32) 354 return; 355 356 xcim = XFixesGetCursorImage(dpy); 357 358 x = xcim->x - xcim->xhot; 359 y = xcim->y - xcim->yhot; 360 361 to_line = FFMIN((y + xcim->height), (height + y_off)); 362 to_column = FFMIN((x + xcim->width), (width + x_off)); 363 364 for (line = FFMAX(y, y_off); line < to_line; line++) { 365 for (column = FFMAX(x, x_off); column < to_column; column++) { 366 int xcim_addr = (line - y) * xcim->width + column - x; 367 int image_addr = ((line - y_off) * width + column - x_off) * pixstride; 368 int r = (uint8_t)(xcim->pixels[xcim_addr] >> 0); 369 int g = (uint8_t)(xcim->pixels[xcim_addr] >> 8); 370 int b = (uint8_t)(xcim->pixels[xcim_addr] >> 16); 371 int a = (uint8_t)(xcim->pixels[xcim_addr] >> 24); 372 373 if (a == 255) { 374 pix[image_addr+0] = r; 375 pix[image_addr+1] = g; 376 pix[image_addr+2] = b; 377 } else if (a) { 378 /* pixel values from XFixesGetCursorImage come premultiplied by alpha */ 379 pix[image_addr+0] = r + (pix[image_addr+0]*(255-a) + 255/2) / 255; 380 pix[image_addr+1] = g + (pix[image_addr+1]*(255-a) + 255/2) / 255; 381 pix[image_addr+2] = b + (pix[image_addr+2]*(255-a) + 255/2) / 255; 382 } 383 } 384 } 385 386 XFree(xcim); 387 xcim = NULL; 388} 389 390 391/** 392 * Read new data in the image structure. 393 * 394 * @param dpy X11 display to grab from 395 * @param d 396 * @param image Image where the grab will be put 397 * @param x Top-Left grabbing rectangle horizontal coordinate 398 * @param y Top-Left grabbing rectangle vertical coordinate 399 * @return 0 if error, !0 if successful 400 */ 401static int 402xget_zpixmap(Display *dpy, Drawable d, XImage *image, int x, int y) 403{ 404 xGetImageReply rep; 405 xGetImageReq *req; 406 long nbytes; 407 408 if (!image) { 409 return 0; 410 } 411 412 LockDisplay(dpy); 413 GetReq(GetImage, req); 414 415 /* First set up the standard stuff in the request */ 416 req->drawable = d; 417 req->x = x; 418 req->y = y; 419 req->width = image->width; 420 req->height = image->height; 421 req->planeMask = (unsigned int)AllPlanes; 422 req->format = ZPixmap; 423 424 if (!_XReply(dpy, (xReply *)&rep, 0, xFalse) || !rep.length) { 425 UnlockDisplay(dpy); 426 SyncHandle(); 427 return 0; 428 } 429 430 nbytes = (long)rep.length << 2; 431 _XReadPad(dpy, image->data, nbytes); 432 433 UnlockDisplay(dpy); 434 SyncHandle(); 435 return 1; 436} 437 438/** 439 * Grab a frame from x11 (public device demuxer API). 440 * 441 * @param s1 Context from avformat core 442 * @param pkt Packet holding the brabbed frame 443 * @return frame size in bytes 444 */ 445static int 446x11grab_read_packet(AVFormatContext *s1, AVPacket *pkt) 447{ 448 struct x11_grab *s = s1->priv_data; 449 Display *dpy = s->dpy; 450 XImage *image = s->image; 451 int x_off = s->x_off; 452 int y_off = s->y_off; 453 454 int screen; 455 Window root; 456 int follow_mouse = s->follow_mouse; 457 458 int64_t curtime, delay; 459 struct timespec ts; 460 461 /* Calculate the time of the next frame */ 462 s->time_frame += INT64_C(1000000); 463 464 /* wait based on the frame rate */ 465 for(;;) { 466 curtime = av_gettime(); 467 delay = s->time_frame * av_q2d(s->time_base) - curtime; 468 if (delay <= 0) { 469 if (delay < INT64_C(-1000000) * av_q2d(s->time_base)) { 470 s->time_frame += INT64_C(1000000); 471 } 472 break; 473 } 474 ts.tv_sec = delay / 1000000; 475 ts.tv_nsec = (delay % 1000000) * 1000; 476 nanosleep(&ts, NULL); 477 } 478 479 av_init_packet(pkt); 480 pkt->data = image->data; 481 pkt->size = s->frame_size; 482 pkt->pts = curtime; 483 484 screen = DefaultScreen(dpy); 485 root = RootWindow(dpy, screen); 486 if (follow_mouse) { 487 int screen_w, screen_h; 488 int pointer_x, pointer_y, _; 489 Window w; 490 491 screen_w = DisplayWidth(dpy, screen); 492 screen_h = DisplayHeight(dpy, screen); 493 XQueryPointer(dpy, root, &w, &w, &pointer_x, &pointer_y, &_, &_, &_); 494 if (follow_mouse == -1) { 495 // follow the mouse, put it at center of grabbing region 496 x_off += pointer_x - s->width / 2 - x_off; 497 y_off += pointer_y - s->height / 2 - y_off; 498 } else { 499 // follow the mouse, but only move the grabbing region when mouse 500 // reaches within certain pixels to the edge. 501 if (pointer_x > x_off + s->width - follow_mouse) { 502 x_off += pointer_x - (x_off + s->width - follow_mouse); 503 } else if (pointer_x < x_off + follow_mouse) 504 x_off -= (x_off + follow_mouse) - pointer_x; 505 if (pointer_y > y_off + s->height - follow_mouse) { 506 y_off += pointer_y - (y_off + s->height - follow_mouse); 507 } else if (pointer_y < y_off + follow_mouse) 508 y_off -= (y_off + follow_mouse) - pointer_y; 509 } 510 // adjust grabbing region position if it goes out of screen. 511 s->x_off = x_off = FFMIN(FFMAX(x_off, 0), screen_w - s->width); 512 s->y_off = y_off = FFMIN(FFMAX(y_off, 0), screen_h - s->height); 513 514 if (s->show_region && s->region_win) 515 XMoveWindow(dpy, s->region_win, 516 s->x_off - REGION_WIN_BORDER, 517 s->y_off - REGION_WIN_BORDER); 518 } 519 520 if (s->show_region) { 521 if (s->region_win) { 522 XEvent evt; 523 // clean up the events, and do the initinal draw or redraw. 524 for (evt.type = NoEventMask; XCheckMaskEvent(dpy, ExposureMask | StructureNotifyMask, &evt); ); 525 if (evt.type) 526 x11grab_draw_region_win(s); 527 } else { 528 x11grab_region_win_init(s); 529 } 530 } 531 532 if(s->use_shm) { 533 if (!XShmGetImage(dpy, root, image, x_off, y_off, AllPlanes)) { 534 av_log (s1, AV_LOG_INFO, "XShmGetImage() failed\n"); 535 } 536 } else { 537 if (!xget_zpixmap(dpy, root, image, x_off, y_off)) { 538 av_log (s1, AV_LOG_INFO, "XGetZPixmap() failed\n"); 539 } 540 } 541 542 if (s->draw_mouse) { 543 paint_mouse_pointer(image, s); 544 } 545 546 return s->frame_size; 547} 548 549/** 550 * Close x11 frame grabber (public device demuxer API). 551 * 552 * @param s1 Context from avformat core 553 * @return 0 success, !0 failure 554 */ 555static int 556x11grab_read_close(AVFormatContext *s1) 557{ 558 struct x11_grab *x11grab = s1->priv_data; 559 560 /* Detach cleanly from shared mem */ 561 if (x11grab->use_shm) { 562 XShmDetach(x11grab->dpy, &x11grab->shminfo); 563 shmdt(x11grab->shminfo.shmaddr); 564 shmctl(x11grab->shminfo.shmid, IPC_RMID, NULL); 565 } 566 567 /* Destroy X11 image */ 568 if (x11grab->image) { 569 XDestroyImage(x11grab->image); 570 x11grab->image = NULL; 571 } 572 573 if (x11grab->region_win) { 574 XDestroyWindow(x11grab->dpy, x11grab->region_win); 575 } 576 577 /* Free X11 display */ 578 XCloseDisplay(x11grab->dpy); 579 return 0; 580} 581 582#define OFFSET(x) offsetof(struct x11_grab, x) 583#define DEC AV_OPT_FLAG_DECODING_PARAM 584static const AVOption options[] = { 585 { "video_size", "A string describing frame size, such as 640x480 or hd720.", OFFSET(video_size), AV_OPT_TYPE_STRING, {.str = "vga"}, 0, 0, DEC }, 586 { "framerate", "", OFFSET(framerate), AV_OPT_TYPE_STRING, {.str = "ntsc"}, 0, 0, DEC }, 587 { "draw_mouse", "Draw the mouse pointer.", OFFSET(draw_mouse), AV_OPT_TYPE_INT, { 1 }, 0, 1, DEC }, 588 { "follow_mouse", "Move the grabbing region when the mouse pointer reaches within specified amount of pixels to the edge of region.", 589 OFFSET(follow_mouse), AV_OPT_TYPE_INT, { 0 }, -1, INT_MAX, DEC, "follow_mouse" }, 590 { "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" }, 591 { "show_region", "Show the grabbing region.", OFFSET(show_region), AV_OPT_TYPE_INT, { 0 }, 0, 1, DEC }, 592 { NULL }, 593}; 594 595static const AVClass x11_class = { 596 .class_name = "X11grab indev", 597 .item_name = av_default_item_name, 598 .option = options, 599 .version = LIBAVUTIL_VERSION_INT, 600}; 601 602/** x11 grabber device demuxer declaration */ 603AVInputFormat ff_x11_grab_device_demuxer = { 604 .name = "x11grab", 605 .long_name = NULL_IF_CONFIG_SMALL("X11grab"), 606 .priv_data_size = sizeof(struct x11_grab), 607 .read_header = x11grab_read_header, 608 .read_packet = x11grab_read_packet, 609 .read_close = x11grab_read_close, 610 .flags = AVFMT_NOFILE, 611 .priv_class = &x11_class, 612}; 613