#include "config.h"
#include <stdlib.h>
+#include <string.h>
#include <xcb/xcb.h>
#if CONFIG_LIBXCB_XFIXES
typedef struct XCBGrabContext {
const AVClass *class;
- uint8_t *buffer;
-
xcb_connection_t *conn;
xcb_screen_t *screen;
xcb_window_t window;
#if CONFIG_LIBXCB_SHM
- xcb_shm_seg_t segment;
+ AVBufferPool *shm_pool;
#endif
int64_t time_frame;
AVRational time_base;
+ int64_t frame_duration;
+ xcb_window_t window_id;
int x, y;
int width, height;
int frame_size;
int show_region;
int region_border;
int centered;
+ int select_region;
- const char *video_size;
const char *framerate;
int has_shm;
#define OFFSET(x) offsetof(XCBGrabContext, x)
#define D AV_OPT_FLAG_DECODING_PARAM
static const AVOption options[] = {
+ { "window_id", "Window to capture.", OFFSET(window_id), AV_OPT_TYPE_INT, { .i64 = XCB_NONE }, 0, UINT32_MAX, D },
{ "x", "Initial x coordinate.", OFFSET(x), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, D },
{ "y", "Initial y coordinate.", OFFSET(y), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, D },
{ "grab_x", "Initial x coordinate.", OFFSET(x), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, D },
{ "grab_y", "Initial y coordinate.", OFFSET(y), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, D },
- { "video_size", "A string describing frame size, such as 640x480 or hd720.", OFFSET(video_size), AV_OPT_TYPE_STRING, {.str = "vga" }, 0, 0, D },
+ { "video_size", "A string describing frame size, such as 640x480 or hd720.", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL }, 0, 0, D },
{ "framerate", "", OFFSET(framerate), AV_OPT_TYPE_STRING, {.str = "ntsc" }, 0, 0, D },
{ "draw_mouse", "Draw the mouse pointer.", OFFSET(draw_mouse), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, D },
{ "follow_mouse", "Move the grabbing region when the mouse pointer reaches within specified amount of pixels to the edge of region.",
{ "centered", "Keep the mouse pointer at the center of grabbing region when following.", 0, AV_OPT_TYPE_CONST, { .i64 = -1 }, INT_MIN, INT_MAX, D, "follow_mouse" },
{ "show_region", "Show the grabbing region.", OFFSET(show_region), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, D },
{ "region_border", "Set the region border thickness.", OFFSET(region_border), AV_OPT_TYPE_INT, { .i64 = 3 }, 1, 128, D },
+ { "select_region", "Select the grabbing region graphically using the pointer.", OFFSET(select_region), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
{ NULL },
};
return 0;
}
+static void xcbgrab_image_reply_free(void *opaque, uint8_t *data)
+{
+ free(opaque);
+}
+
static int xcbgrab_frame(AVFormatContext *s, AVPacket *pkt)
{
XCBGrabContext *c = s->priv_data;
xcb_get_image_cookie_t iq;
xcb_get_image_reply_t *img;
- xcb_drawable_t drawable = c->screen->root;
+ xcb_drawable_t drawable = c->window_id;
xcb_generic_error_t *e = NULL;
uint8_t *data;
- int length, ret;
+ int length;
iq = xcb_get_image(c->conn, XCB_IMAGE_FORMAT_Z_PIXMAP, drawable,
c->x, c->y, c->width, c->height, ~0);
data = xcb_get_image_data(img);
length = xcb_get_image_data_length(img);
- ret = av_new_packet(pkt, length);
-
- if (!ret)
- memcpy(pkt->data, data, length);
+ pkt->buf = av_buffer_create(data, length, xcbgrab_image_reply_free, img, 0);
+ if (!pkt->buf) {
+ free(img);
+ return AVERROR(ENOMEM);
+ }
- free(img);
+ pkt->data = data;
+ pkt->size = length;
- return ret;
+ return 0;
}
-static void wait_frame(AVFormatContext *s, AVPacket *pkt)
+static int64_t wait_frame(AVFormatContext *s, AVPacket *pkt)
{
XCBGrabContext *c = s->priv_data;
int64_t curtime, delay;
- int64_t frame_time = av_rescale_q(1, c->time_base, AV_TIME_BASE_Q);
- c->time_frame += frame_time;
+ c->time_frame += c->frame_duration;
for (;;) {
- curtime = av_gettime();
+ curtime = av_gettime_relative();
delay = c->time_frame - curtime;
if (delay <= 0)
break;
av_usleep(delay);
}
- pkt->pts = curtime;
+ return curtime;
}
#if CONFIG_LIBXCB_SHM
return 0;
}
-static int allocate_shm(AVFormatContext *s)
+static void free_shm_buffer(void *opaque, uint8_t *data)
{
- XCBGrabContext *c = s->priv_data;
- int size = c->frame_size + AV_INPUT_BUFFER_PADDING_SIZE;
+ shmdt(data);
+}
+
+static AVBufferRef *allocate_shm_buffer(void *opaque, size_t size)
+{
+ xcb_connection_t *conn = opaque;
+ xcb_shm_seg_t segment;
+ AVBufferRef *ref;
uint8_t *data;
int id;
- if (c->buffer)
- return 0;
id = shmget(IPC_PRIVATE, size, IPC_CREAT | 0777);
- if (id == -1) {
- char errbuf[1024];
- int err = AVERROR(errno);
- av_strerror(err, errbuf, sizeof(errbuf));
- av_log(s, AV_LOG_ERROR, "Cannot get %d bytes of shared memory: %s.\n",
- size, errbuf);
- return err;
- }
- xcb_shm_attach(c->conn, c->segment, id, 0);
+ if (id == -1)
+ return NULL;
+
+ segment = xcb_generate_id(conn);
+ xcb_shm_attach(conn, segment, id, 0);
data = shmat(id, NULL, 0);
shmctl(id, IPC_RMID, 0);
if ((intptr_t)data == -1 || !data)
- return AVERROR(errno);
- c->buffer = data;
- return 0;
+ return NULL;
+
+ ref = av_buffer_create(data, size, free_shm_buffer, (void *)(ptrdiff_t)segment, 0);
+ if (!ref)
+ shmdt(data);
+
+ return ref;
}
static int xcbgrab_frame_shm(AVFormatContext *s, AVPacket *pkt)
XCBGrabContext *c = s->priv_data;
xcb_shm_get_image_cookie_t iq;
xcb_shm_get_image_reply_t *img;
- xcb_drawable_t drawable = c->screen->root;
+ xcb_drawable_t drawable = c->window_id;
xcb_generic_error_t *e = NULL;
- int ret;
+ AVBufferRef *buf;
+ xcb_shm_seg_t segment;
- ret = allocate_shm(s);
- if (ret < 0)
- return ret;
+ buf = av_buffer_pool_get(c->shm_pool);
+ if (!buf) {
+ av_log(s, AV_LOG_ERROR, "Could not get shared memory buffer.\n");
+ return AVERROR(ENOMEM);
+ }
+ segment = (xcb_shm_seg_t)av_buffer_pool_buffer_get_opaque(buf);
iq = xcb_shm_get_image(c->conn, drawable,
c->x, c->y, c->width, c->height, ~0,
- XCB_IMAGE_FORMAT_Z_PIXMAP, c->segment, 0);
+ XCB_IMAGE_FORMAT_Z_PIXMAP, segment, 0);
img = xcb_shm_get_image_reply(c->conn, iq, &e);
xcb_flush(c->conn);
e->sequence, e->resource_id, e->minor_code, e->major_code);
free(e);
+ av_buffer_unref(&buf);
return AVERROR(EACCES);
}
free(img);
- pkt->data = c->buffer;
+ pkt->buf = buf;
+ pkt->data = buf->data;
pkt->size = c->frame_size;
return 0;
static void xcbgrab_draw_mouse(AVFormatContext *s, AVPacket *pkt,
xcb_query_pointer_reply_t *p,
- xcb_get_geometry_reply_t *geo)
+ xcb_get_geometry_reply_t *geo,
+ int win_x, int win_y)
{
XCBGrabContext *gr = s->priv_data;
uint32_t *cursor;
cx = ci->x - ci->xhot;
cy = ci->y - ci->yhot;
- x = FFMAX(cx, gr->x);
- y = FFMAX(cy, gr->y);
+ x = FFMAX(cx, win_x + gr->x);
+ y = FFMAX(cy, win_y + gr->y);
- w = FFMIN(cx + ci->width, gr->x + gr->width) - x;
- h = FFMIN(cy + ci->height, gr->y + gr->height) - y;
+ w = FFMIN(cx + ci->width, win_x + gr->x + gr->width) - x;
+ h = FFMIN(cy + ci->height, win_y + gr->y + gr->height) - y;
c_off = x - cx;
- i_off = x - gr->x;
+ i_off = x - gr->x - win_x;
cursor += (y - cy) * ci->width;
- image += (y - gr->y) * gr->width * stride;
+ image += (y - gr->y - win_y) * gr->width * stride;
for (y = 0; y < h; y++) {
cursor += c_off;
}
#endif /* CONFIG_LIBXCB_XFIXES */
-static void xcbgrab_update_region(AVFormatContext *s)
+static void xcbgrab_update_region(AVFormatContext *s, int win_x, int win_y)
{
XCBGrabContext *c = s->priv_data;
- const uint32_t args[] = { c->x - c->region_border,
- c->y - c->region_border };
+ const uint32_t args[] = { win_x + c->x - c->region_border,
+ win_y + c->y - c->region_border };
xcb_configure_window(c->conn,
c->window,
XCBGrabContext *c = s->priv_data;
xcb_query_pointer_cookie_t pc;
xcb_get_geometry_cookie_t gc;
+ xcb_translate_coordinates_cookie_t tc;
xcb_query_pointer_reply_t *p = NULL;
xcb_get_geometry_reply_t *geo = NULL;
+ xcb_translate_coordinates_reply_t *translate = NULL;
int ret = 0;
+ int64_t pts;
+ int win_x = 0, win_y = 0;
wait_frame(s, pkt);
+ pts = av_gettime();
if (c->follow_mouse || c->draw_mouse) {
- pc = xcb_query_pointer(c->conn, c->screen->root);
- gc = xcb_get_geometry(c->conn, c->screen->root);
+ pc = xcb_query_pointer(c->conn, c->window_id);
+ gc = xcb_get_geometry(c->conn, c->window_id);
p = xcb_query_pointer_reply(c->conn, pc, NULL);
+ if (!p) {
+ av_log(s, AV_LOG_ERROR, "Failed to query xcb pointer\n");
+ return AVERROR_EXTERNAL;
+ }
geo = xcb_get_geometry_reply(c->conn, gc, NULL);
+ if (!geo) {
+ av_log(s, AV_LOG_ERROR, "Failed to get xcb geometry\n");
+ free(p);
+ return AVERROR_EXTERNAL;
+ }
+ }
+ if (c->window_id != c->screen->root) {
+ tc = xcb_translate_coordinates(c->conn, c->window_id, c->screen->root, 0, 0);
+ translate = xcb_translate_coordinates_reply(c->conn, tc, NULL);
+ if (!translate) {
+ free(p);
+ free(geo);
+ av_log(s, AV_LOG_ERROR, "Failed to translate xcb geometry\n");
+ return AVERROR_EXTERNAL;
+ }
+ win_x = translate->dst_x;
+ win_y = translate->dst_y;
+ free(translate);
}
if (c->follow_mouse && p->same_screen)
xcbgrab_reposition(s, p, geo);
if (c->show_region)
- xcbgrab_update_region(s);
+ xcbgrab_update_region(s, win_x, win_y);
#if CONFIG_LIBXCB_SHM
- if (c->has_shm && xcbgrab_frame_shm(s, pkt) < 0)
+ if (c->has_shm && xcbgrab_frame_shm(s, pkt) < 0) {
+ av_log(s, AV_LOG_WARNING, "Continuing without shared memory.\n");
c->has_shm = 0;
+ }
#endif
if (!c->has_shm)
ret = xcbgrab_frame(s, pkt);
+ pkt->dts = pkt->pts = pts;
+ pkt->duration = c->frame_duration;
#if CONFIG_LIBXCB_XFIXES
if (ret >= 0 && c->draw_mouse && p->same_screen)
- xcbgrab_draw_mouse(s, pkt, p, geo);
+ xcbgrab_draw_mouse(s, pkt, p, geo, win_x, win_y);
#endif
free(p);
XCBGrabContext *ctx = s->priv_data;
#if CONFIG_LIBXCB_SHM
- if (ctx->buffer) {
- shmdt(ctx->buffer);
- }
+ av_buffer_pool_uninit(&ctx->shm_pool);
#endif
xcb_disconnect(ctx->conn);
}
static int pixfmt_from_pixmap_format(AVFormatContext *s, int depth,
- int *pix_fmt)
+ int *pix_fmt, int *bpp)
{
XCBGrabContext *c = s->priv_data;
const xcb_setup_t *setup = xcb_get_setup(c->conn);
switch (depth) {
case 32:
if (fmt->bits_per_pixel == 32)
- *pix_fmt = AV_PIX_FMT_0RGB;
+ *pix_fmt = setup->image_byte_order == XCB_IMAGE_ORDER_LSB_FIRST ?
+ AV_PIX_FMT_BGR0 : AV_PIX_FMT_0RGB;
break;
case 24:
if (fmt->bits_per_pixel == 32)
- *pix_fmt = AV_PIX_FMT_0RGB32;
+ *pix_fmt = setup->image_byte_order == XCB_IMAGE_ORDER_LSB_FIRST ?
+ AV_PIX_FMT_BGR0 : AV_PIX_FMT_0RGB;
else if (fmt->bits_per_pixel == 24)
- *pix_fmt = AV_PIX_FMT_RGB24;
+ *pix_fmt = setup->image_byte_order == XCB_IMAGE_ORDER_LSB_FIRST ?
+ AV_PIX_FMT_BGR24 : AV_PIX_FMT_RGB24;
break;
case 16:
if (fmt->bits_per_pixel == 16)
- *pix_fmt = AV_PIX_FMT_RGB565;
+ *pix_fmt = setup->image_byte_order == XCB_IMAGE_ORDER_LSB_FIRST ?
+ AV_PIX_FMT_RGB565LE : AV_PIX_FMT_RGB565BE;
break;
case 15:
if (fmt->bits_per_pixel == 16)
- *pix_fmt = AV_PIX_FMT_RGB555;
+ *pix_fmt = setup->image_byte_order == XCB_IMAGE_ORDER_LSB_FIRST ?
+ AV_PIX_FMT_RGB555LE : AV_PIX_FMT_RGB555BE;
break;
case 8:
if (fmt->bits_per_pixel == 8)
}
if (*pix_fmt) {
- c->bpp = fmt->bits_per_pixel;
- c->frame_size = c->width * c->height * fmt->bits_per_pixel / 8;
+ *bpp = fmt->bits_per_pixel;
return 0;
}
AVStream *st = avformat_new_stream(s, NULL);
xcb_get_geometry_cookie_t gc;
xcb_get_geometry_reply_t *geo;
+ int64_t frame_size_bits;
int ret;
if (!st)
return AVERROR(ENOMEM);
- ret = av_parse_video_size(&c->width, &c->height, c->video_size);
- if (ret < 0)
- return ret;
-
ret = av_parse_video_rate(&st->avg_frame_rate, c->framerate);
if (ret < 0)
return ret;
avpriv_set_pts_info(st, 64, 1, 1000000);
- gc = xcb_get_geometry(c->conn, c->screen->root);
+ gc = xcb_get_geometry(c->conn, c->window_id);
geo = xcb_get_geometry_reply(c->conn, gc, NULL);
- if (!geo)
+ if (!geo) {
+ av_log(s, AV_LOG_ERROR, "Can't find window '0x%x', aborting.\n", c->window_id);
return AVERROR_EXTERNAL;
+ }
+
+ if (!c->width || !c->height) {
+ c->width = geo->width;
+ c->height = geo->height;
+ }
if (c->x + c->width > geo->width ||
c->y + c->height > geo->height) {
c->time_base = (AVRational){ st->avg_frame_rate.den,
st->avg_frame_rate.num };
- c->time_frame = av_gettime();
+ c->frame_duration = av_rescale_q(1, c->time_base, AV_TIME_BASE_Q);
+ c->time_frame = av_gettime_relative();
+
+ ret = pixfmt_from_pixmap_format(s, geo->depth, &st->codecpar->format, &c->bpp);
+ free(geo);
+ if (ret < 0)
+ return ret;
+
+ frame_size_bits = (int64_t)c->width * c->height * c->bpp;
+ if (frame_size_bits / 8 + AV_INPUT_BUFFER_PADDING_SIZE > INT_MAX) {
+ av_log(s, AV_LOG_ERROR, "Captured area is too large\n");
+ return AVERROR_PATCHWELCOME;
+ }
+ c->frame_size = frame_size_bits / 8;
+
+#if CONFIG_LIBXCB_SHM
+ c->shm_pool = av_buffer_pool_init2(c->frame_size + AV_INPUT_BUFFER_PADDING_SIZE,
+ c->conn, allocate_shm_buffer, NULL);
+ if (!c->shm_pool)
+ return AVERROR(ENOMEM);
+#endif
st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
st->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO;
st->codecpar->width = c->width;
st->codecpar->height = c->height;
-
- ret = pixfmt_from_pixmap_format(s, geo->depth, &st->codecpar->format);
-
- free(geo);
+ st->codecpar->bit_rate = av_rescale(frame_size_bits, st->avg_frame_rate.num, st->avg_frame_rate.den);
return ret;
}
draw_rectangle(s);
}
+#define CROSSHAIR_CURSOR 34
+
+static xcb_rectangle_t rectangle_from_corners(xcb_point_t *corner_a,
+ xcb_point_t *corner_b)
+{
+ xcb_rectangle_t rectangle;
+ rectangle.x = FFMIN(corner_a->x, corner_b->x);
+ rectangle.y = FFMIN(corner_a->y, corner_b->y);
+ rectangle.width = FFABS(corner_a->x - corner_b->x);
+ rectangle.height = FFABS(corner_a->y - corner_b->y);
+ return rectangle;
+}
+
+static int select_region(AVFormatContext *s)
+{
+ XCBGrabContext *c = s->priv_data;
+ xcb_connection_t *conn = c->conn;
+ xcb_screen_t *screen = c->screen;
+
+ int ret = 0, done = 0, was_pressed = 0;
+ xcb_cursor_t cursor;
+ xcb_font_t cursor_font;
+ xcb_point_t press_position;
+ xcb_generic_event_t *event;
+ xcb_rectangle_t rectangle = { 0 };
+ xcb_grab_pointer_reply_t *reply;
+ xcb_grab_pointer_cookie_t cookie;
+
+ xcb_window_t root_window = screen->root;
+ xcb_gcontext_t gc = xcb_generate_id(conn);
+ uint32_t mask = XCB_GC_FUNCTION | XCB_GC_SUBWINDOW_MODE;
+ uint32_t values[] = { XCB_GX_INVERT, XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS };
+ xcb_create_gc(conn, gc, root_window, mask, values);
+
+ cursor_font = xcb_generate_id(conn);
+ xcb_open_font(conn, cursor_font, strlen("cursor"), "cursor");
+ cursor = xcb_generate_id(conn);
+ xcb_create_glyph_cursor(conn, cursor, cursor_font, cursor_font,
+ CROSSHAIR_CURSOR, CROSSHAIR_CURSOR + 1, 0, 0, 0,
+ 0xFFFF, 0xFFFF, 0xFFFF);
+ cookie = xcb_grab_pointer(conn, 0, root_window,
+ XCB_EVENT_MASK_BUTTON_PRESS |
+ XCB_EVENT_MASK_BUTTON_RELEASE |
+ XCB_EVENT_MASK_BUTTON_MOTION,
+ XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
+ root_window, cursor, XCB_CURRENT_TIME);
+ reply = xcb_grab_pointer_reply(conn, cookie, NULL);
+ if (!reply || reply->status != XCB_GRAB_STATUS_SUCCESS) {
+ av_log(s, AV_LOG_ERROR,
+ "Failed to select region. Could not grab pointer.\n");
+ ret = AVERROR(EIO);
+ free(reply);
+ goto fail;
+ }
+ free(reply);
+
+ xcb_grab_server(conn);
+
+ while (!done && (event = xcb_wait_for_event(conn))) {
+ switch (event->response_type & ~0x80) {
+ case XCB_BUTTON_PRESS: {
+ xcb_button_press_event_t *press = (xcb_button_press_event_t *)event;
+ press_position = (xcb_point_t){ press->event_x, press->event_y };
+ rectangle.x = press_position.x;
+ rectangle.y = press_position.y;
+ xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
+ was_pressed = 1;
+ break;
+ }
+ case XCB_MOTION_NOTIFY: {
+ if (was_pressed) {
+ xcb_motion_notify_event_t *motion =
+ (xcb_motion_notify_event_t *)event;
+ xcb_point_t cursor_position = { motion->event_x, motion->event_y };
+ xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
+ rectangle = rectangle_from_corners(&press_position, &cursor_position);
+ xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
+ }
+ break;
+ }
+ case XCB_BUTTON_RELEASE: {
+ xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
+ done = 1;
+ break;
+ }
+ default:
+ break;
+ }
+ xcb_flush(conn);
+ free(event);
+ }
+ c->width = rectangle.width;
+ c->height = rectangle.height;
+ if (c->width && c->height) {
+ c->x = rectangle.x;
+ c->y = rectangle.y;
+ } else {
+ c->x = 0;
+ c->y = 0;
+ }
+ xcb_ungrab_server(conn);
+ xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
+ xcb_flush(conn);
+
+fail:
+ xcb_free_cursor(conn, cursor);
+ xcb_close_font(conn, cursor_font);
+ xcb_free_gc(conn, gc);
+ return ret;
+}
+
static av_cold int xcbgrab_read_header(AVFormatContext *s)
{
XCBGrabContext *c = s->priv_data;
return AVERROR(EIO);
}
+ if (c->window_id == XCB_NONE)
+ c->window_id = c->screen->root;
+ else {
+ if (c->select_region) {
+ av_log(s, AV_LOG_WARNING, "select_region ignored with window_id.\n");
+ c->select_region = 0;
+ }
+ if (c->follow_mouse) {
+ av_log(s, AV_LOG_WARNING, "follow_mouse ignored with window_id.\n");
+ c->follow_mouse = 0;
+ }
+ }
+
+ if (c->select_region) {
+ ret = select_region(s);
+ if (ret < 0) {
+ xcbgrab_read_close(s);
+ return ret;
+ }
+ }
+
ret = create_stream(s);
if (ret < 0) {
}
#if CONFIG_LIBXCB_SHM
- if ((c->has_shm = check_shm(c->conn)))
- c->segment = xcb_generate_id(c->conn);
+ c->has_shm = check_shm(c->conn);
#endif
#if CONFIG_LIBXCB_XFIXES