avdevice/xcbgrab: Add option for grabbing a window

The option allows to select a specific window instead of the whole
screen.

Reviewed-by: Andriy Gelman <andriy.gelman@gmail.com>
Signed-off-by: Andriy Gelman <andriy.gelman@gmail.com>
This commit is contained in:
sgerwk 2021-02-10 17:36:00 +01:00 committed by Andriy Gelman
parent 1af4885014
commit 07de796b5d
2 changed files with 66 additions and 19 deletions

View File

@ -1564,8 +1564,21 @@ With @var{follow_mouse}:
ffmpeg -f x11grab -follow_mouse centered -show_region 1 -framerate 25 -video_size cif -i :0.0 out.mpg
@end example
@item window_id
Grab this window, instead of the whole screen. Default value is 0, which maps to
the whole screen (root window).
The id of a window can be found using the @command{xwininfo} program, possibly with options -tree and
-root.
If the window is later enlarged, the new area is not recorded. Video ends when
the window is closed, unmapped (i.e., iconified) or shrunk beyond the video
size (which defaults to the initial window size).
This option disables options @option{follow_mouse} and @option{select_region}.
@item video_size
Set the video frame size. Default is the full desktop.
Set the video frame size. Default is the full desktop or window.
@item grab_x
@item grab_y

View File

@ -60,6 +60,7 @@ typedef struct XCBGrabContext {
AVRational time_base;
int64_t frame_duration;
xcb_window_t window_id;
int x, y;
int width, height;
int frame_size;
@ -82,6 +83,7 @@ typedef struct XCBGrabContext {
#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 },
@ -157,7 +159,7 @@ 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;
@ -267,7 +269,7 @@ 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;
AVBufferRef *buf;
xcb_shm_seg_t segment;
@ -333,7 +335,8 @@ static int check_xfixes(xcb_connection_t *conn)
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;
@ -355,17 +358,17 @@ static void xcbgrab_draw_mouse(AVFormatContext *s, AVPacket *pkt,
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;
@ -400,11 +403,11 @@ static void xcbgrab_draw_mouse(AVFormatContext *s, AVPacket *pkt,
}
#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,
@ -417,17 +420,20 @@ static int xcbgrab_read_packet(AVFormatContext *s, AVPacket *pkt)
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");
@ -440,12 +446,25 @@ static int xcbgrab_read_packet(AVFormatContext *s, AVPacket *pkt)
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) {
@ -460,7 +479,7 @@ static int xcbgrab_read_packet(AVFormatContext *s, AVPacket *pkt)
#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);
@ -572,10 +591,12 @@ static int create_stream(AVFormatContext *s)
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;
@ -831,6 +852,19 @@ static av_cold int xcbgrab_read_header(AVFormatContext *s)
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) {