mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2024-12-30 19:32:13 +00:00
8e0a4d2d4d
xserver defines the endianness of the grabbed images. Use this information to set the correct pixel format. This also fixes format selection in configuration depth=32/bpp=32 with xserver on a little endian machine. Before the patch, the big endian layout 0RGB was always selected which is incorrect because BGR0 should be used. RGB24 was also incorrectly assumed (but this format was removed in xserver 1.20). The big-endian settings can be tested using docker+qemu from a little-endian machine: $ docker run --rm --privileged multiarch/qemu-user-static --reset -p yes $ docker run --rm -it -v /tmp:/tmp powerpc64/debian /bin/bash In docker container $ apt-get update $ apt-get install xvfb $ apt-get install x11-apps To test AV_PIX_FMT_0RGB32 $ Xvfb :2 -screen 0 720x480x24 & $ export DISPLAY=:2 $ xclock -geometry 720x480 -bg green #test different colors On your host machine grab the frames using the following command. View output to check that colors are rendered correctly $ ./ffmpeg -y -f x11grab -i :2.0 -codec:v mpeg2video out.mp4 Other pixel formats can be tested by modifying how Xvfb is started in the docker container: AV_PIX_FMT_RGB565 $ Xvfb :2 -screen 0 720x480x16 AV_PIX_FMT_RGB555 $ Xvfb :2 -screen 0 720x480x15 AV_PIX_FMT_BGR24 / AV_PIX_FMT_RGB24 This is difficult to test because bpp=24 support was removed in xserver 1.20 https://lists.x.org/archives/xorg-devel/2018-February/056175.html?hmsr=joyk.com&utm_source=joyk.com&utm_medium=referral However, I was able to run previous version of Xvfb (with some modifications to force 24bpp) to check that images are rendered correctly. Reviewed-by: Carl Eugen Hoyos <ceffmpeg@gmail.com> Signed-off-by: Andriy Gelman <andriy.gelman@gmail.com>
882 lines
26 KiB
C
882 lines
26 KiB
C
/*
|
|
* XCB input grabber
|
|
* Copyright (C) 2014 Luca Barbato <lu_zero@gentoo.org>
|
|
*
|
|
* This file is part of FFmpeg.
|
|
*
|
|
* FFmpeg is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* FFmpeg is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with FFmpeg; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <xcb/xcb.h>
|
|
|
|
#if CONFIG_LIBXCB_XFIXES
|
|
#include <xcb/xfixes.h>
|
|
#endif
|
|
|
|
#if CONFIG_LIBXCB_SHM
|
|
#include <sys/shm.h>
|
|
#include <xcb/shm.h>
|
|
#endif
|
|
|
|
#if CONFIG_LIBXCB_SHAPE
|
|
#include <xcb/shape.h>
|
|
#endif
|
|
|
|
#include "libavutil/internal.h"
|
|
#include "libavutil/mathematics.h"
|
|
#include "libavutil/opt.h"
|
|
#include "libavutil/parseutils.h"
|
|
#include "libavutil/time.h"
|
|
|
|
#include "libavformat/avformat.h"
|
|
#include "libavformat/internal.h"
|
|
|
|
typedef struct XCBGrabContext {
|
|
const AVClass *class;
|
|
|
|
xcb_connection_t *conn;
|
|
xcb_screen_t *screen;
|
|
xcb_window_t window;
|
|
#if CONFIG_LIBXCB_SHM
|
|
AVBufferPool *shm_pool;
|
|
#endif
|
|
int64_t time_frame;
|
|
AVRational time_base;
|
|
int64_t frame_duration;
|
|
|
|
int x, y;
|
|
int width, height;
|
|
int frame_size;
|
|
int bpp;
|
|
|
|
int draw_mouse;
|
|
int follow_mouse;
|
|
int show_region;
|
|
int region_border;
|
|
int centered;
|
|
int select_region;
|
|
|
|
const char *framerate;
|
|
|
|
int has_shm;
|
|
} XCBGrabContext;
|
|
|
|
#define FOLLOW_CENTER -1
|
|
|
|
#define OFFSET(x) offsetof(XCBGrabContext, x)
|
|
#define D AV_OPT_FLAG_DECODING_PARAM
|
|
static const AVOption options[] = {
|
|
{ "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(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.",
|
|
OFFSET(follow_mouse), AV_OPT_TYPE_INT, { .i64 = 0 }, FOLLOW_CENTER, INT_MAX, D, "follow_mouse" },
|
|
{ "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 },
|
|
};
|
|
|
|
static const AVClass xcbgrab_class = {
|
|
.class_name = "xcbgrab indev",
|
|
.item_name = av_default_item_name,
|
|
.option = options,
|
|
.version = LIBAVUTIL_VERSION_INT,
|
|
.category = AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT,
|
|
};
|
|
|
|
static int xcbgrab_reposition(AVFormatContext *s,
|
|
xcb_query_pointer_reply_t *p,
|
|
xcb_get_geometry_reply_t *geo)
|
|
{
|
|
XCBGrabContext *c = s->priv_data;
|
|
int x = c->x, y = c->y;
|
|
int w = c->width, h = c->height, f = c->follow_mouse;
|
|
int p_x, p_y;
|
|
|
|
if (!p || !geo)
|
|
return AVERROR(EIO);
|
|
|
|
p_x = p->win_x;
|
|
p_y = p->win_y;
|
|
|
|
if (f == FOLLOW_CENTER) {
|
|
x = p_x - w / 2;
|
|
y = p_y - h / 2;
|
|
} else {
|
|
int left = x + f;
|
|
int right = x + w - f;
|
|
int top = y + f;
|
|
int bottom = y + h - f;
|
|
if (p_x > right) {
|
|
x += p_x - right;
|
|
} else if (p_x < left) {
|
|
x -= left - p_x;
|
|
}
|
|
if (p_y > bottom) {
|
|
y += p_y - bottom;
|
|
} else if (p_y < top) {
|
|
y -= top - p_y;
|
|
}
|
|
}
|
|
|
|
c->x = FFMIN(FFMAX(0, x), geo->width - w);
|
|
c->y = FFMIN(FFMAX(0, y), geo->height - h);
|
|
|
|
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_generic_error_t *e = NULL;
|
|
uint8_t *data;
|
|
int length;
|
|
|
|
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) {
|
|
av_log(s, AV_LOG_ERROR,
|
|
"Cannot get the image data "
|
|
"event_error: response_type:%u error_code:%u "
|
|
"sequence:%u resource_id:%u minor_code:%u major_code:%u.\n",
|
|
e->response_type, e->error_code,
|
|
e->sequence, e->resource_id, e->minor_code, e->major_code);
|
|
free(e);
|
|
return AVERROR(EACCES);
|
|
}
|
|
|
|
if (!img)
|
|
return AVERROR(EAGAIN);
|
|
|
|
data = xcb_get_image_data(img);
|
|
length = xcb_get_image_data_length(img);
|
|
|
|
av_init_packet(pkt);
|
|
|
|
pkt->buf = av_buffer_create(data, length, xcbgrab_image_reply_free, img, 0);
|
|
if (!pkt->buf) {
|
|
free(img);
|
|
return AVERROR(ENOMEM);
|
|
}
|
|
|
|
pkt->data = data;
|
|
pkt->size = length;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int64_t wait_frame(AVFormatContext *s, AVPacket *pkt)
|
|
{
|
|
XCBGrabContext *c = s->priv_data;
|
|
int64_t curtime, delay;
|
|
|
|
c->time_frame += c->frame_duration;
|
|
|
|
for (;;) {
|
|
curtime = av_gettime();
|
|
delay = c->time_frame - curtime;
|
|
if (delay <= 0)
|
|
break;
|
|
av_usleep(delay);
|
|
}
|
|
|
|
return curtime;
|
|
}
|
|
|
|
#if CONFIG_LIBXCB_SHM
|
|
static int check_shm(xcb_connection_t *conn)
|
|
{
|
|
xcb_shm_query_version_cookie_t cookie = xcb_shm_query_version(conn);
|
|
xcb_shm_query_version_reply_t *reply;
|
|
|
|
reply = xcb_shm_query_version_reply(conn, cookie, NULL);
|
|
if (reply) {
|
|
free(reply);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void free_shm_buffer(void *opaque, uint8_t *data)
|
|
{
|
|
shmdt(data);
|
|
}
|
|
|
|
static AVBufferRef *allocate_shm_buffer(void *opaque, int size)
|
|
{
|
|
xcb_connection_t *conn = opaque;
|
|
xcb_shm_seg_t segment;
|
|
AVBufferRef *ref;
|
|
uint8_t *data;
|
|
int id;
|
|
|
|
id = shmget(IPC_PRIVATE, size, IPC_CREAT | 0777);
|
|
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 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_generic_error_t *e = NULL;
|
|
AVBufferRef *buf;
|
|
xcb_shm_seg_t segment;
|
|
|
|
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, segment, 0);
|
|
img = xcb_shm_get_image_reply(c->conn, iq, &e);
|
|
|
|
xcb_flush(c->conn);
|
|
|
|
if (e) {
|
|
av_log(s, AV_LOG_ERROR,
|
|
"Cannot get the image data "
|
|
"event_error: response_type:%u error_code:%u "
|
|
"sequence:%u resource_id:%u minor_code:%u major_code:%u.\n",
|
|
e->response_type, e->error_code,
|
|
e->sequence, e->resource_id, e->minor_code, e->major_code);
|
|
|
|
free(e);
|
|
av_buffer_unref(&buf);
|
|
return AVERROR(EACCES);
|
|
}
|
|
|
|
free(img);
|
|
|
|
av_init_packet(pkt);
|
|
|
|
pkt->buf = buf;
|
|
pkt->data = buf->data;
|
|
pkt->size = c->frame_size;
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_LIBXCB_SHM */
|
|
|
|
#if CONFIG_LIBXCB_XFIXES
|
|
static int check_xfixes(xcb_connection_t *conn)
|
|
{
|
|
xcb_xfixes_query_version_cookie_t cookie;
|
|
xcb_xfixes_query_version_reply_t *reply;
|
|
|
|
cookie = xcb_xfixes_query_version(conn, XCB_XFIXES_MAJOR_VERSION,
|
|
XCB_XFIXES_MINOR_VERSION);
|
|
reply = xcb_xfixes_query_version_reply(conn, cookie, NULL);
|
|
|
|
if (reply) {
|
|
free(reply);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define BLEND(target, source, alpha) \
|
|
(target) + ((source) * (255 - (alpha)) + 255 / 2) / 255
|
|
|
|
static void xcbgrab_draw_mouse(AVFormatContext *s, AVPacket *pkt,
|
|
xcb_query_pointer_reply_t *p,
|
|
xcb_get_geometry_reply_t *geo)
|
|
{
|
|
XCBGrabContext *gr = s->priv_data;
|
|
uint32_t *cursor;
|
|
uint8_t *image = pkt->data;
|
|
int stride = gr->bpp / 8;
|
|
xcb_xfixes_get_cursor_image_cookie_t cc;
|
|
xcb_xfixes_get_cursor_image_reply_t *ci;
|
|
int cx, cy, x, y, w, h, c_off, i_off;
|
|
|
|
cc = xcb_xfixes_get_cursor_image(gr->conn);
|
|
ci = xcb_xfixes_get_cursor_image_reply(gr->conn, cc, NULL);
|
|
if (!ci)
|
|
return;
|
|
|
|
cursor = xcb_xfixes_get_cursor_image_cursor_image(ci);
|
|
if (!cursor)
|
|
return;
|
|
|
|
cx = ci->x - ci->xhot;
|
|
cy = ci->y - ci->yhot;
|
|
|
|
x = FFMAX(cx, gr->x);
|
|
y = FFMAX(cy, gr->y);
|
|
|
|
w = FFMIN(cx + ci->width, gr->x + gr->width) - x;
|
|
h = FFMIN(cy + ci->height, gr->y + gr->height) - y;
|
|
|
|
c_off = x - cx;
|
|
i_off = x - gr->x;
|
|
|
|
cursor += (y - cy) * ci->width;
|
|
image += (y - gr->y) * gr->width * stride;
|
|
|
|
for (y = 0; y < h; y++) {
|
|
cursor += c_off;
|
|
image += i_off * stride;
|
|
for (x = 0; x < w; x++, cursor++, image += stride) {
|
|
int r, g, b, a;
|
|
|
|
r = *cursor & 0xff;
|
|
g = (*cursor >> 8) & 0xff;
|
|
b = (*cursor >> 16) & 0xff;
|
|
a = (*cursor >> 24) & 0xff;
|
|
|
|
if (!a)
|
|
continue;
|
|
|
|
if (a == 255) {
|
|
image[0] = r;
|
|
image[1] = g;
|
|
image[2] = b;
|
|
} else {
|
|
image[0] = BLEND(r, image[0], a);
|
|
image[1] = BLEND(g, image[1], a);
|
|
image[2] = BLEND(b, image[2], a);
|
|
}
|
|
|
|
}
|
|
cursor += ci->width - w - c_off;
|
|
image += (gr->width - w - i_off) * stride;
|
|
}
|
|
|
|
free(ci);
|
|
}
|
|
#endif /* CONFIG_LIBXCB_XFIXES */
|
|
|
|
static void xcbgrab_update_region(AVFormatContext *s)
|
|
{
|
|
XCBGrabContext *c = s->priv_data;
|
|
const uint32_t args[] = { c->x - c->region_border,
|
|
c->y - c->region_border };
|
|
|
|
xcb_configure_window(c->conn,
|
|
c->window,
|
|
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y,
|
|
args);
|
|
}
|
|
|
|
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_query_pointer_reply_t *p = NULL;
|
|
xcb_get_geometry_reply_t *geo = NULL;
|
|
int ret = 0;
|
|
int64_t pts;
|
|
|
|
pts = wait_frame(s, pkt);
|
|
|
|
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);
|
|
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->follow_mouse && p->same_screen)
|
|
xcbgrab_reposition(s, p, geo);
|
|
|
|
if (c->show_region)
|
|
xcbgrab_update_region(s);
|
|
|
|
#if CONFIG_LIBXCB_SHM
|
|
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);
|
|
#endif
|
|
|
|
free(p);
|
|
free(geo);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static av_cold int xcbgrab_read_close(AVFormatContext *s)
|
|
{
|
|
XCBGrabContext *ctx = s->priv_data;
|
|
|
|
#if CONFIG_LIBXCB_SHM
|
|
av_buffer_pool_uninit(&ctx->shm_pool);
|
|
#endif
|
|
|
|
xcb_disconnect(ctx->conn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static xcb_screen_t *get_screen(const xcb_setup_t *setup, int screen_num)
|
|
{
|
|
xcb_screen_iterator_t it = xcb_setup_roots_iterator(setup);
|
|
xcb_screen_t *screen = NULL;
|
|
|
|
for (; it.rem > 0; xcb_screen_next (&it)) {
|
|
if (!screen_num) {
|
|
screen = it.data;
|
|
break;
|
|
}
|
|
|
|
screen_num--;
|
|
}
|
|
|
|
return screen;
|
|
}
|
|
|
|
static int pixfmt_from_pixmap_format(AVFormatContext *s, int depth,
|
|
int *pix_fmt, int *bpp)
|
|
{
|
|
XCBGrabContext *c = s->priv_data;
|
|
const xcb_setup_t *setup = xcb_get_setup(c->conn);
|
|
const xcb_format_t *fmt = xcb_setup_pixmap_formats(setup);
|
|
int length = xcb_setup_pixmap_formats_length(setup);
|
|
|
|
*pix_fmt = 0;
|
|
|
|
while (length--) {
|
|
if (fmt->depth == depth) {
|
|
switch (depth) {
|
|
case 32:
|
|
if (fmt->bits_per_pixel == 32)
|
|
*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 = 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 = 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 = 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 = 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)
|
|
*pix_fmt = AV_PIX_FMT_RGB8;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (*pix_fmt) {
|
|
*bpp = fmt->bits_per_pixel;
|
|
return 0;
|
|
}
|
|
|
|
fmt++;
|
|
}
|
|
avpriv_report_missing_feature(s, "Mapping this pixmap format");
|
|
|
|
return AVERROR_PATCHWELCOME;
|
|
}
|
|
|
|
static int create_stream(AVFormatContext *s)
|
|
{
|
|
XCBGrabContext *c = s->priv_data;
|
|
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_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);
|
|
geo = xcb_get_geometry_reply(c->conn, gc, NULL);
|
|
if (!geo)
|
|
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) {
|
|
av_log(s, AV_LOG_ERROR,
|
|
"Capture area %dx%d at position %d.%d "
|
|
"outside the screen size %dx%d\n",
|
|
c->width, c->height,
|
|
c->x, c->y,
|
|
geo->width, geo->height);
|
|
free(geo);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
c->time_base = (AVRational){ st->avg_frame_rate.den,
|
|
st->avg_frame_rate.num };
|
|
c->frame_duration = av_rescale_q(1, c->time_base, AV_TIME_BASE_Q);
|
|
c->time_frame = av_gettime();
|
|
|
|
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;
|
|
st->codecpar->bit_rate = av_rescale(frame_size_bits, st->avg_frame_rate.num, st->avg_frame_rate.den);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void draw_rectangle(AVFormatContext *s)
|
|
{
|
|
XCBGrabContext *c = s->priv_data;
|
|
xcb_gcontext_t gc = xcb_generate_id(c->conn);
|
|
uint32_t mask = XCB_GC_FOREGROUND |
|
|
XCB_GC_BACKGROUND |
|
|
XCB_GC_LINE_WIDTH |
|
|
XCB_GC_LINE_STYLE |
|
|
XCB_GC_FILL_STYLE;
|
|
uint32_t values[] = { c->screen->black_pixel,
|
|
c->screen->white_pixel,
|
|
c->region_border,
|
|
XCB_LINE_STYLE_DOUBLE_DASH,
|
|
XCB_FILL_STYLE_SOLID };
|
|
xcb_rectangle_t r = { 1, 1,
|
|
c->width + c->region_border * 2 - 3,
|
|
c->height + c->region_border * 2 - 3 };
|
|
|
|
xcb_create_gc(c->conn, gc, c->window, mask, values);
|
|
|
|
xcb_poly_rectangle(c->conn, c->window, gc, 1, &r);
|
|
}
|
|
|
|
static void setup_window(AVFormatContext *s)
|
|
{
|
|
XCBGrabContext *c = s->priv_data;
|
|
uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
|
|
uint32_t values[] = { 1,
|
|
XCB_EVENT_MASK_EXPOSURE |
|
|
XCB_EVENT_MASK_STRUCTURE_NOTIFY };
|
|
av_unused xcb_rectangle_t rect = { 0, 0, c->width, c->height };
|
|
|
|
c->window = xcb_generate_id(c->conn);
|
|
|
|
xcb_create_window(c->conn, XCB_COPY_FROM_PARENT,
|
|
c->window,
|
|
c->screen->root,
|
|
c->x - c->region_border,
|
|
c->y - c->region_border,
|
|
c->width + c->region_border * 2,
|
|
c->height + c->region_border * 2,
|
|
0,
|
|
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
|
XCB_COPY_FROM_PARENT,
|
|
mask, values);
|
|
|
|
#if CONFIG_LIBXCB_SHAPE
|
|
xcb_shape_rectangles(c->conn, XCB_SHAPE_SO_SUBTRACT,
|
|
XCB_SHAPE_SK_BOUNDING, XCB_CLIP_ORDERING_UNSORTED,
|
|
c->window,
|
|
c->region_border, c->region_border,
|
|
1, &rect);
|
|
#endif
|
|
|
|
xcb_map_window(c->conn, c->window);
|
|
|
|
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;
|
|
int screen_num, ret;
|
|
const xcb_setup_t *setup;
|
|
char *display_name = av_strdup(s->url);
|
|
|
|
if (!display_name)
|
|
return AVERROR(ENOMEM);
|
|
|
|
if (!sscanf(s->url, "%[^+]+%d,%d", display_name, &c->x, &c->y)) {
|
|
*display_name = 0;
|
|
sscanf(s->url, "+%d,%d", &c->x, &c->y);
|
|
}
|
|
|
|
c->conn = xcb_connect(display_name[0] ? display_name : NULL, &screen_num);
|
|
av_freep(&display_name);
|
|
|
|
if ((ret = xcb_connection_has_error(c->conn))) {
|
|
av_log(s, AV_LOG_ERROR, "Cannot open display %s, error %d.\n",
|
|
s->url[0] ? s->url : "default", ret);
|
|
return AVERROR(EIO);
|
|
}
|
|
|
|
setup = xcb_get_setup(c->conn);
|
|
|
|
c->screen = get_screen(setup, screen_num);
|
|
if (!c->screen) {
|
|
av_log(s, AV_LOG_ERROR, "The screen %d does not exist.\n",
|
|
screen_num);
|
|
xcbgrab_read_close(s);
|
|
return AVERROR(EIO);
|
|
}
|
|
|
|
if (c->select_region) {
|
|
ret = select_region(s);
|
|
if (ret < 0) {
|
|
xcbgrab_read_close(s);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = create_stream(s);
|
|
|
|
if (ret < 0) {
|
|
xcbgrab_read_close(s);
|
|
return ret;
|
|
}
|
|
|
|
#if CONFIG_LIBXCB_SHM
|
|
c->has_shm = check_shm(c->conn);
|
|
#endif
|
|
|
|
#if CONFIG_LIBXCB_XFIXES
|
|
if (c->draw_mouse) {
|
|
if (!(c->draw_mouse = check_xfixes(c->conn))) {
|
|
av_log(s, AV_LOG_WARNING,
|
|
"XFixes not available, cannot draw the mouse.\n");
|
|
}
|
|
if (c->bpp < 24) {
|
|
avpriv_report_missing_feature(s, "%d bits per pixel screen",
|
|
c->bpp);
|
|
c->draw_mouse = 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (c->show_region)
|
|
setup_window(s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
AVInputFormat ff_xcbgrab_demuxer = {
|
|
.name = "x11grab",
|
|
.long_name = NULL_IF_CONFIG_SMALL("X11 screen capture, using XCB"),
|
|
.priv_data_size = sizeof(XCBGrabContext),
|
|
.read_header = xcbgrab_read_header,
|
|
.read_packet = xcbgrab_read_packet,
|
|
.read_close = xcbgrab_read_close,
|
|
.flags = AVFMT_NOFILE,
|
|
.priv_class = &xcbgrab_class,
|
|
};
|