[FFmpeg-devel] [PATCH] feature: xcbgrab single window

Ing. Radomír Polách rp at t4d.cz
Tue Nov 8 04:57:22 EET 2016


Allows to grab only a single window by using syntax:
ffmpeg -f x11grab -r 15 -i ":0/0x0580001b/parent2" ...
The syntax for input is "display/grab_window_id/focus_window_id".
If focus_window_id is omitted it is set as grab_window_id.
There are special constants for focus_window_id:
- this: grab_window_id
- parent: parent window id of grab_window,
- parent2: grand parent window id of grab_window,
- parent3: great grand parent window id of grab_window.

Has a single command line option repeat_frame which controls
out of focus streaming. Turned on in default for repeating
the last frame. If turned off sends empty (zero length) packet
to the ffmpeg backend.
---
 Changelog             |   1 +
 libavdevice/xcbgrab.c | 210 +++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 201 insertions(+), 10 deletions(-)

diff --git a/Changelog b/Changelog
index 11a769a..eb83365 100644
--- a/Changelog
+++ b/Changelog
@@ -3,6 +3,7 @@ releases are sorted from youngest to oldest.
 
 version <next>:
 - CrystalHD decoder moved to new decode API
+- XCB-based screen-grabbing of single window
 
 version 3.2:
 - libopenmpt demuxer
diff --git a/libavdevice/xcbgrab.c b/libavdevice/xcbgrab.c
index 702e66c..a27cdee 100644
--- a/libavdevice/xcbgrab.c
+++ b/libavdevice/xcbgrab.c
@@ -1,6 +1,8 @@
 /*
  * XCB input grabber
  * Copyright (C) 2014 Luca Barbato <lu_zero at gentoo.org>
+ * Copyright (C) 2016 Radomír Polách <rp at t4d.cz>
+ * Copyright (C) 2016 Kristýna Dudová <kd at t4d.cz>
  *
  * This file is part of FFmpeg.
  *
@@ -70,11 +72,21 @@ typedef struct XCBGrabContext {
     int show_region;
     int region_border;
     int centered;
+    int repeat_frame;
 
     const char *video_size;
     const char *framerate;
 
     int has_shm;
+
+    char *focus_name;
+    char *grab_name;
+    xcb_window_t focus_window;
+    xcb_window_t grab_window;
+
+    uint8_t *data;
+    int size;
+    int warned;
 } XCBGrabContext;
 
 #define FOLLOW_CENTER -1
@@ -88,6 +100,7 @@ static const AVOption options[] = {
     { "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 },
     { "framerate", "", OFFSET(framerate), AV_OPT_TYPE_STRING, {.str = "ntsc" }, 0, 0, D },
+    { "repeat_frame", "Repeat last frame, when window is not active or data are not available.", OFFSET(repeat_frame), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, 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.",
       OFFSET(follow_mouse), AV_OPT_TYPE_INT, { .i64 = 0 },  FOLLOW_CENTER, INT_MAX, D, "follow_mouse" },
@@ -156,9 +169,13 @@ static int xcbgrab_frame(AVFormatContext *s, AVPacket *pkt)
     uint8_t *data;
     int length, ret;
 
-    iq  = xcb_get_image(c->conn, XCB_IMAGE_FORMAT_Z_PIXMAP, drawable,
-                        c->x, c->y, c->width, c->height, ~0);
-
+    if (c->focus_name) {
+        iq  = xcb_get_image(c->conn, XCB_IMAGE_FORMAT_Z_PIXMAP, c->grab_window,
+                            0, 0, c->width, c->height, ~0);
+    } else {
+        iq  = xcb_get_image(c->conn, XCB_IMAGE_FORMAT_Z_PIXMAP, drawable,
+                            c->x, c->y, c->width, c->height, ~0);
+    }
     img = xcb_get_image_reply(c->conn, iq, &e);
 
     if (e) {
@@ -261,9 +278,16 @@ static int xcbgrab_frame_shm(AVFormatContext *s, AVPacket *pkt)
     if (ret < 0)
         return ret;
 
-    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);
+    if (c->focus_name) {
+        iq = xcb_shm_get_image(c->conn, c->grab_window,
+                               0, 0, c->width, c->height, ~0,
+                               XCB_IMAGE_FORMAT_Z_PIXMAP, c->segment, 0);
+    } else {
+        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);
+    }
+
     img = xcb_shm_get_image_reply(c->conn, iq, &e);
 
     xcb_flush(c->conn);
@@ -329,8 +353,13 @@ static void xcbgrab_draw_mouse(AVFormatContext *s, AVPacket *pkt,
     if (!cursor)
         return;
 
-    cx = ci->x - ci->xhot;
-    cy = ci->y - ci->yhot;
+    if (gr->focus_name) {
+        cx = p->win_x - ci->xhot;
+        cy = p->win_y - ci->yhot;
+    } else {
+        cx = ci->x - ci->xhot;
+        cy = ci->y - ci->yhot;
+    }
 
     x = FFMAX(cx, gr->x);
     y = FFMAX(cy, gr->y);
@@ -389,6 +418,68 @@ static void xcbgrab_update_region(AVFormatContext *s)
                          args);
 }
 
+static xcb_window_t get_window_focus(AVFormatContext *s)
+{
+    XCBGrabContext *ctx = s->priv_data;
+    xcb_window_t w = 0;
+    xcb_get_input_focus_cookie_t c;
+    xcb_get_input_focus_reply_t *r;
+
+    c = xcb_get_input_focus(ctx->conn);
+    r = xcb_get_input_focus_reply(ctx->conn, c, NULL);
+    if (!r)
+        return -1;
+
+    w = r->focus;
+    free(r);
+    return w;
+}
+
+static xcb_window_t get_window_parent(AVFormatContext *s, xcb_window_t w)
+{
+    XCBGrabContext *ctx = s->priv_data;
+    xcb_query_tree_cookie_t c;
+    xcb_query_tree_reply_t *r;
+    xcb_generic_error_t *e;
+
+    c = xcb_query_tree(ctx->conn, w);
+    r = xcb_query_tree_reply(ctx->conn, c, &e);
+    if (!r)
+        return -1;
+
+    w = r->parent;
+    free(r);
+    return w;
+}
+
+static void xcbgrab_store_packet(AVFormatContext *s, AVPacket *pkt) {
+    XCBGrabContext *c = s->priv_data;
+
+    c->size = pkt->size;
+
+    if (c->size) {
+        if (!c->data)  {
+            c->data = av_malloc(c->size);
+        }
+        memcpy(c->data, pkt->data, c->size);
+    }
+}
+
+static int xcbgrab_load_packet(AVFormatContext *s, AVPacket *pkt) {
+    XCBGrabContext *c = s->priv_data;
+    int ret;
+
+    if (!c->size)
+        return 0;
+
+    ret = av_new_packet(pkt, c->size);
+
+    if (!ret)
+        memcpy(pkt->data, c->data, c->size);
+
+    return ret;
+}
+
 static int xcbgrab_read_packet(AVFormatContext *s, AVPacket *pkt)
 {
     XCBGrabContext *c = s->priv_data;
@@ -400,11 +491,56 @@ static int xcbgrab_read_packet(AVFormatContext *s, AVPacket *pkt)
 
     wait_frame(s, pkt);
 
+    if (c->focus_name) {
+        xcb_window_t w = get_window_focus(s);
+        if (w != c->focus_window) {
+            if (!c->warned) {
+                c->warned = 1;
+                av_log(s, AV_LOG_WARNING,
+                       "Not grabbing, focus window not focused, focused window is 0x%08x.\n", w);
+            }
+            if (c->repeat_frame) {
+                return xcbgrab_load_packet(s, pkt);
+            } else {
+                return 0;
+            }
+        } else {
+            if (c->warned == 1) {
+                c->warned = 0;
+                av_log(s, AV_LOG_WARNING,
+                       "Grabbing resumed.\n");
+            }
+        }
+    }
+
     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);
+        if (c->focus_name) {
+            pc  = xcb_query_pointer(c->conn, c->grab_window);
+            gc  = xcb_get_geometry(c->conn, c->grab_window);
+        } else {
+            pc  = xcb_query_pointer(c->conn, c->screen->root);
+            gc  = xcb_get_geometry(c->conn, c->screen->root);
+        }
         p   = xcb_query_pointer_reply(c->conn, pc, NULL);
         geo = xcb_get_geometry_reply(c->conn, gc, NULL);
+        if (geo->width < c->width || geo->height < c->height) {
+            if (!c->warned) {
+                c->warned = 2;
+                av_log(s, AV_LOG_WARNING,
+                    "Not grabbing, grab window width or height lower than initial.\n");
+            }
+            if (c->repeat_frame) {
+                return xcbgrab_load_packet(s, pkt);
+            } else {
+                return 0;
+            }
+        } else {
+            if (c->warned == 2) {
+                c->warned = 0;
+                av_log(s, AV_LOG_WARNING,
+                    "Grabbing resumed.\n");
+            }
+        }
     }
 
     if (c->follow_mouse && p->same_screen)
@@ -425,6 +561,10 @@ static int xcbgrab_read_packet(AVFormatContext *s, AVPacket *pkt)
         xcbgrab_draw_mouse(s, pkt, p, geo);
 #endif
 
+    if (c->repeat_frame) {
+        xcbgrab_store_packet(s, pkt);
+    }
+
     free(p);
     free(geo);
 
@@ -443,6 +583,10 @@ static av_cold int xcbgrab_read_close(AVFormatContext *s)
 
     xcb_disconnect(ctx->conn);
 
+    if (ctx->data)  {
+        free(ctx->data);
+    }
+
     return 0;
 }
 
@@ -535,6 +679,19 @@ static int create_stream(AVFormatContext *s)
 
     avpriv_set_pts_info(st, 64, 1, 1000000);
 
+    if (c->focus_name) {
+        gc  = xcb_get_geometry(c->conn, c->grab_window);
+        geo = xcb_get_geometry_reply(c->conn, gc, NULL);
+        if (!geo) {
+            av_log(s, AV_LOG_ERROR,
+                   "Grab window 0x%08x does not exist.\n",
+                   c->grab_window);
+            return AVERROR(EINVAL);
+        }
+        c->width = geo->width & ~1;
+        c->height = geo->height & ~1;
+    }
+
     gc  = xcb_get_geometry(c->conn, c->screen->root);
     geo = xcb_get_geometry_reply(c->conn, gc, NULL);
 
@@ -630,6 +787,17 @@ static av_cold int xcbgrab_read_header(AVFormatContext *s)
     int screen_num, ret;
     const xcb_setup_t *setup;
     char *display_name = av_strdup(s->filename);
+    if (c->focus_name = strchr(s->filename, '/')) {
+        c->focus_name[0] = '\0';
+        ++c->focus_name;
+    }
+    if (c->grab_name = strchr(c->focus_name, '/')) {
+        c->grab_name[0] = '\0';
+        ++c->grab_name;
+    }
+    c->data = NULL;
+    c->size = 0;
+    c->warned = 0;
 
     if (!display_name)
         return AVERROR(ENOMEM);
@@ -658,6 +826,28 @@ static av_cold int xcbgrab_read_header(AVFormatContext *s)
         return AVERROR(EIO);
     }
 
+    if (c->focus_name) {
+        c->focus_window = strtoul(c->focus_name, NULL, 16);
+        if (c->grab_name) {
+            if (!strcmp(c->grab_name,"this")) {
+                c->grab_window = c->focus_window;
+            } else if (!strcmp(c->grab_name,"parent")) {
+                c->grab_window = get_window_parent(s, c->focus_window);
+            } else if (!strcmp(c->grab_name,"parent2")) {
+                c->grab_window = get_window_parent(s, c->focus_window);
+                c->grab_window = get_window_parent(s, c->grab_window);
+            } else if (!strcmp(c->grab_name,"parent3")) {
+                c->grab_window = get_window_parent(s, c->focus_window);
+                c->grab_window = get_window_parent(s, c->grab_window);
+                c->grab_window = get_window_parent(s, c->grab_window);
+            } else {
+                c->grab_window = strtoul(c->grab_name, NULL, 16);
+            }
+        } else {
+            c->grab_window = get_window_parent(s, c->focus_window);
+        }
+    }
+
     ret = create_stream(s);
 
     if (ret < 0) {
-- 
2.7.4



More information about the ffmpeg-devel mailing list