[FFmpeg-devel] [PATCH] Screen frame grabbing for Win32 platform (bump)
Vitor Sessak
vitor1001
Sun Jan 23 17:13:00 CET 2011
On 03/24/2010 02:07 AM, Christophe Gisquet wrote:
> 2010/3/24 Christophe Gisquet<christophe.gisquet at gmail.com>:
>> Best regards,
>> Kurosu
Git-friendly patch attached so patchwork will catch it up.
-Vitor
>From christophe.gisquetatgmail.com Sun Jan 23 17:11:12 2011
From: christophe.gisquetatgmail.com (Christophe Gisquet)
Date: Sun, 23 Jan 2011 17:11:12 +0100
Subject: [PATCH] Add Win32 GDI-based screen grabbing
Message-ID: <mailman.463.1295799187.1307.ffmpeg-devel at mplayerhq.hu>
---
Changelog | 1 +
configure | 4 +
doc/ffmpeg-doc.texi | 27 ++++
doc/general.texi | 1 +
libavdevice/Makefile | 1 +
libavdevice/alldevices.c | 1 +
libavdevice/avdevice.h | 2 +-
libavdevice/gdigrab.c | 376 ++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 412 insertions(+), 1 deletions(-)
create mode 100644 libavdevice/gdigrab.c
diff --git a/Changelog b/Changelog
index 7627b68..ebf60ee 100644
--- a/Changelog
+++ b/Changelog
@@ -65,6 +65,7 @@ version <next>:
- Kega Game Video (KGV1) decoder
- VorbisComment writing for FLAC, Ogg FLAC and Ogg Speex files
- RTP depacketization of Theora
+- GDI screen grabbing for Windows
diff --git a/configure b/configure
index 0682006..b28c06d 100755
--- a/configure
+++ b/configure
@@ -1403,6 +1403,8 @@ vfwcap_indev_deps="capCreateCaptureWindow"
vfwcap_indev_extralibs="-lavicap32"
x11_grab_device_indev_deps="x11grab XShmCreateImage"
x11_grab_device_indev_extralibs="-lX11 -lXext -lXfixes"
+gdi_grab_device_indev_deps="GetBitmapBits"
+gdi_grab_device_indev_extralibs="-lgdi32"
# protocols
gopher_protocol_deps="network"
@@ -2701,6 +2703,8 @@ check_func XOpenDisplay -lX11 &&
check_func XShmCreateImage -lX11 -lXext &&
check_func XFixesGetCursorImage -lX11 -lXext -lXfixes
+check_func_headers "windows.h" GetBitmapBits "$gdi_grab_device_indev_extralibs"
+
if ! disabled vdpau && enabled vdpau_vdpau_h; then
check_cpp_condition \
vdpau/vdpau.h "defined VDP_DECODER_PROFILE_MPEG4_PART2_ASP" ||
diff --git a/doc/ffmpeg-doc.texi b/doc/ffmpeg-doc.texi
index 4a5f8d0..05b2388 100644
--- a/doc/ffmpeg-doc.texi
+++ b/doc/ffmpeg-doc.texi
@@ -57,6 +57,33 @@ ffmpeg -f x11grab -s cif -i :0.0+10,20 /tmp/out.mpg
0.0 is display.screen number of your X11 server, same as the DISPLAY environment
variable. 10 is the x-offset and 20 the y-offset for the grabbing.
+ at section Win32 GDI grabbing
+
+FFmpeg can grab the Windows display through the old GDI interface.
+An input filename is required; it is usually used to pass specific GDI
+parameters but any unknown parameter is ignored.
+
+ at example
+ffmpeg -f gdigrab -s cif -i desktop /tmp/out.mpg
+ at end example
+
+Most of the time, specifying the capture size (here through -s cif) is not
+required, as a default size based on the desktop or window size will be
+determined. But if you only want to capture a smaller area, do mention it.
+
+You can add through the filename ':'-delimited options:
+
+ at example
+ffmpeg -f gdigrab -i offset=10,20:cursor:title=Hello /tmp/out.mpg
+ at end example
+
+where:
+- 'offset=x-offset:y-offset' specifies the offset from the origin
+- 'cursor' requests to paint the mouse cursor
+- 'desktop' requests to capture the whole desktop
+- 'title=name' specifies the name of the window to capture (if none, the
+whole desktop is captured)
+
@section Video and Audio file format conversion
* FFmpeg can use any supported file format and protocol as input:
diff --git a/doc/general.texi b/doc/general.texi
index 53af7ca..795ca42 100644
--- a/doc/general.texi
+++ b/doc/general.texi
@@ -708,6 +708,7 @@ performance on systems without hardware floating point support).
@item Video4Linux2 @tab X @tab
@item VfW capture @tab X @tab
@item X11 grabbing @tab X @tab
+ at item Win32 grabbing @tab X @tab
@end multitable
@code{X} means that input/output is supported.
diff --git a/libavdevice/Makefile b/libavdevice/Makefile
index a0c3858..511f90c 100644
--- a/libavdevice/Makefile
+++ b/libavdevice/Makefile
@@ -21,6 +21,7 @@ OBJS-$(CONFIG_V4L2_INDEV) += v4l2.o
OBJS-$(CONFIG_V4L_INDEV) += v4l.o
OBJS-$(CONFIG_VFWCAP_INDEV) += vfwcap.o
OBJS-$(CONFIG_X11_GRAB_DEVICE_INDEV) += x11grab.o
+OBJS-$(CONFIG_GDI_GRAB_DEVICE_INDEV) += gdigrab.o
# external libraries
OBJS-$(CONFIG_LIBDC1394_INDEV) += libdc1394.o
diff --git a/libavdevice/alldevices.c b/libavdevice/alldevices.c
index e7a9a5e..1840b52 100644
--- a/libavdevice/alldevices.c
+++ b/libavdevice/alldevices.c
@@ -49,6 +49,7 @@ void avdevice_register_all(void)
REGISTER_INDEV (V4L, v4l);
REGISTER_INDEV (VFWCAP, vfwcap);
REGISTER_INDEV (X11_GRAB_DEVICE, x11_grab_device);
+ REGISTER_INDEV (GDI_GRAB_DEVICE, gdi_grab_device);
/* external libraries */
REGISTER_INDEV (LIBDC1394, libdc1394);
diff --git a/libavdevice/avdevice.h b/libavdevice/avdevice.h
index dcd835c..27631d3 100644
--- a/libavdevice/avdevice.h
+++ b/libavdevice/avdevice.h
@@ -22,7 +22,7 @@
#include "libavutil/avutil.h"
#define LIBAVDEVICE_VERSION_MAJOR 52
-#define LIBAVDEVICE_VERSION_MINOR 2
+#define LIBAVDEVICE_VERSION_MINOR 3
#define LIBAVDEVICE_VERSION_MICRO 0
#define LIBAVDEVICE_VERSION_INT AV_VERSION_INT(LIBAVDEVICE_VERSION_MAJOR, \
diff --git a/libavdevice/gdigrab.c b/libavdevice/gdigrab.c
new file mode 100644
index 0000000..99da97e
--- /dev/null
+++ b/libavdevice/gdigrab.c
@@ -0,0 +1,376 @@
+/*
+ * GDI video grab interface
+ *
+ * This file is part of FFmpeg.
+ *
+ * Copyright (C) 2007-2010 Christophe Gisquet <word1.word2 <at> gmail.com>
+ *
+ * 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
+ */
+
+/**
+ * @file libavdevice/gdigrab.c
+ * GDI frame device demuxer by Christophe Gisquet <word1.word2 <at> gmail.com>
+ */
+
+#include "config.h"
+#include "libavformat/avformat.h"
+#include <windows.h>
+
+/**
+ * GDI Device Demuxer context
+ */
+typedef struct
+{
+ HWND window_handle; /**< handle of the window for the grab */
+ HDC source_hdc; /**< Source device context */
+ HDC window_hdc; /**< Destination, source-compatible device context */
+ HBITMAP hbmp; /**< Information on the bitmap captured */
+
+ AVRational time_base; /**< Time base */
+ int64_t time_frame; /**< Current time */
+
+ int x_off; /**< Horizontal top-left corner coordinate */
+ int y_off; /**< Vertical top-left corner coordinate */
+ int cursor; /**< Also capture cursor */
+
+ int size; /**< Size in bytes of the grab frame */
+ int width; /**< Width of the grab frame */
+ int height; /**< Height of the grab frame */
+ int bpp; /**< Bits per pixel of the grab frame */
+ int printed; /**< Error message for printing cursor
+ already printed */
+} gdi_grab;
+
+#define WIN32_API_ERROR(str) \
+ av_log(s1, AV_LOG_ERROR, str " (error %li)\n", GetLastError())
+
+/**
+ * Initializes the gdi grab device demuxer (public device demuxer API).
+ *
+ * Format: <option1>=<param1>:<options2>=<param2>
+ * offset=%i,%i: offset on x and y against the final source window
+ * cursor: grab cursor
+ * title=%s\0: window caption, must be last option
+ *
+ * @param s1 Context from avformat core
+ * @param ap Parameters from avformat core
+ * @return AVERROR_IO error, 0 success
+ */
+static int gdigrab_read_header(AVFormatContext *s1, AVFormatParameters *ap)
+{
+ gdi_grab *s = s1->priv_data;
+ const char *param = s1->filename;
+ char *name = NULL;
+ AVStream *st = NULL;
+ int desktop = 0;
+ int input_pixfmt;
+ int screenwidth;
+ int screenheight;
+ BITMAP bmp;
+
+ if(!ap->time_base.den) {
+ av_log(s, AV_LOG_ERROR, "A time base must be specified.\n");
+ return AVERROR(EIO);
+ }
+
+ do {
+ if (!strncmp(param, "desktop", 7))
+ desktop = 1;
+ else if (!strncmp(param, "offset=", 7))
+ sscanf(param+7, "%i,%i", &s->x_off, &s->y_off);
+ else if (!strncmp(param, "cursor", 6))
+ s->cursor = 1;
+ else if (!strncmp(param, "title=", 6)) {
+ // Move to parameter *and* look for next parameter
+ const char *end = strchr((param+=6), ':');
+ int size;
+ if (end) size = end - param;
+ else size = strlen(s1->filename) - (param-s1->filename);
+
+ name = av_malloc(size+1);
+ memcpy(name, param, size); name[size] = 0;
+
+ s->window_handle = FindWindow(NULL, name);
+ if (!s->window_handle) {
+ av_log(s1, AV_LOG_ERROR,
+ "Can't find window '%s', aborting.\n", name);
+ goto error;
+ }
+ param += size-1;
+ }
+ param++;
+ } while ((param = strchr(param, ':')+1) != (void*)1);
+
+ if (!s->window_handle && !desktop) {
+ av_log(s1, AV_LOG_ERROR,
+ "Please use desktop or title= to specify your target.\n");
+ goto error;
+ }
+
+ s->source_hdc = GetDC(s->window_handle);
+ if (!s->source_hdc) {
+ WIN32_API_ERROR("Couldn't get window DC");
+ goto error;
+ }
+
+ screenwidth = GetDeviceCaps(s->source_hdc, HORZRES);
+ screenheight = GetDeviceCaps(s->source_hdc, VERTRES);
+
+ // Determine the size of the area captured
+ if (!ap->width || !ap->height) {
+ if (s->window_handle) {
+ RECT dim;
+ GetClientRect(s->window_handle, &dim);
+ s->width = dim.right-dim.left;
+ s->height = dim.bottom-dim.top;
+ } else {
+ s->width = screenwidth;
+ s->height = screenheight;
+ }
+
+ if (s->width < s->x_off || s->height < s->y_off) {
+ av_log(s1, AV_LOG_ERROR,
+ "Offset (%i,%i) too big for window size %ix%i, aborting.\n",
+ s->x_off, s->y_off, s->width, s->height);
+ goto error;
+ }
+ s->width -= s->x_off;
+ s->height -= s->y_off;
+ } else {
+ s->width = ap->width;
+ s->height = ap->height;
+ }
+
+ if (s->x_off + s->width > screenwidth || s->y_off + s->height > screenheight) {
+ av_log(s1, AV_LOG_ERROR,
+ "Offset (%i,%i) and capture size (%ix%i) too big for screen size %ix%i.\n",
+ s->x_off, s->y_off, s->width, s->height, screenwidth, screenheight);
+ goto error;
+ }
+
+ s->bpp = GetDeviceCaps(s->source_hdc, BITSPIXEL);
+
+ if (name) {
+ av_log(s1, AV_LOG_INFO,
+ "Found window %s, capturing %ix%ix%i at (%i,%i)\n",
+ name, s->width, s->height, s->bpp, s->x_off, s->y_off);
+ av_free(name);
+ } else {
+ av_log(s1, AV_LOG_INFO,
+ "Capturing whole desktop as %ix%ix%i at (%i,%i)\n",
+ s->width, s->height, s->bpp, s->x_off, s->y_off);
+ }
+
+ if (s->width < 0 || s->height < 0 || s->bpp%8) {
+ av_log(s1, AV_LOG_ERROR, "Invalid properties, aborting\n");
+ return AVERROR(EIO);
+ }
+
+ s->window_hdc = CreateCompatibleDC(s->source_hdc);
+ if (!s->window_hdc) {
+ WIN32_API_ERROR("Screen DC CreateCompatibleDC");
+ return AVERROR(EIO);
+ }
+ s->hbmp = CreateCompatibleBitmap(s->source_hdc, s->width, s->height);
+ if (!s->hbmp) {
+ WIN32_API_ERROR("Screen DC CreateCompatibleBitmap");
+ return AVERROR(EIO);
+ }
+
+ /* Get info from the bitmap */
+ GetObject(s->hbmp, sizeof(BITMAP), &bmp);
+ av_log(s1, AV_LOG_DEBUG,
+ "Using Bitmap type %li, size %lix%lix%i,\n"
+ "%i planes of width %li bytes\n",
+ bmp.bmType, bmp.bmWidth, bmp.bmHeight, bmp.bmBitsPixel,
+ bmp.bmPlanes, bmp.bmWidthBytes);
+ if (!SelectObject(s->window_hdc, s->hbmp)) {
+ WIN32_API_ERROR("SelectObject");
+ return AVERROR(EIO);
+ }
+
+ switch (s->bpp) {
+ case 8: input_pixfmt = PIX_FMT_PAL8; break;
+ case 16: input_pixfmt = PIX_FMT_RGB555; break;
+ case 24: input_pixfmt = PIX_FMT_BGR24; break;
+ case 32: input_pixfmt = PIX_FMT_RGB32; break;
+ default:
+ av_log(s1, AV_LOG_ERROR,
+ "image depth %i not supported... aborting\n", s->bpp);
+ return -1;
+ }
+ s->size = bmp.bmWidthBytes*bmp.bmHeight*bmp.bmPlanes;
+
+ st = av_new_stream(s1, 0);
+ if (!st)
+ return AVERROR(ENOMEM);
+
+ av_set_pts_info(st, 64, 1, 1000000); /* 64 bits pts in us */
+ st->codec->codec_type = CODEC_TYPE_VIDEO;
+ st->codec->codec_id = CODEC_ID_RAWVIDEO;
+ st->codec->width = s->width;
+ st->codec->height = s->height;
+ st->codec->pix_fmt = input_pixfmt;
+ st->codec->time_base = ap->time_base;
+ st->codec->bit_rate = s->size * 1/av_q2d(ap->time_base) * 8;
+
+ return 0;
+
+ error:
+ av_free(name);
+ return AVERROR(EIO);
+}
+
+/**
+ * Paints a mouse pointer in a Win32 image.
+ *
+ * @param s1 Context of the log information
+ * @param s Current grad structure
+ */
+static void paint_mouse_pointer(AVFormatContext *s1, gdi_grab *s)
+{
+ CURSORINFO ci;
+
+#define CURSOR_ERROR(str) \
+ if (!s->printed) { \
+ WIN32_API_ERROR(str); \
+ s->printed = 1; \
+ }
+
+ ci.cbSize = sizeof(ci);
+
+ if (GetCursorInfo(&ci) && ci.flags == CURSOR_SHOWING) {
+ HICON icon = CopyIcon(ci.hCursor);
+ ICONINFO info;
+ if(GetIconInfo(icon, &info)) {
+ long int x = ci.ptScreenPos.x - info.xHotspot;
+ long int y = ci.ptScreenPos.y - info.yHotspot;
+
+ if (s->window_handle) {
+ RECT rect;
+
+ if (GetWindowRect(s->window_handle, &rect)) {
+ av_log(s1, AV_LOG_DEBUG, "Pos(%li,%li) -> (%li,%li)\n",
+ x, y, x - rect.left, y - rect.top);
+ x -= rect.left;
+ y -= rect.top;
+ } else {
+ CURSOR_ERROR("Couldn't get icon rectangle");
+ }
+ }
+
+ if (!DrawIcon(s->window_hdc, x, y, icon))
+ CURSOR_ERROR("Couldn't draw icon");
+ } else {
+ CURSOR_ERROR("Couldn't get cursor info");
+ }
+
+ DestroyIcon(icon);
+ } else {
+ CURSOR_ERROR("Cursor not showing?");
+ }
+}
+
+
+
+/**
+ * Grabs a frame from gdi (public device demuxer API).
+ *
+ * @param s1 Context from avformat core
+ * @param pkt Packet holding the grabbed frame
+ * @return frame size in bytes
+ */
+static int gdigrab_read_packet(AVFormatContext *s1, AVPacket *pkt)
+{
+ gdi_grab *s = s1->priv_data;
+ int64_t curtime, delay;
+
+ /* Calculate the time of the next frame */
+ s->time_frame += INT64_C(1000000);
+
+ /* wait based on the frame rate */
+ while (1) {
+ curtime = av_gettime();
+ delay = s->time_frame * av_q2d(s->time_base) - curtime;
+ if (delay <= 0) {
+ if (delay < INT64_C(-1000000) * av_q2d(s->time_base)) {
+ s->time_frame += INT64_C(1000000);
+ }
+ break;
+ }
+ if (s1->flags & AVFMT_FLAG_NONBLOCK)
+ return AVERROR(EAGAIN);
+ else
+ Sleep(delay/1000);
+ }
+
+ if (av_new_packet(pkt, s->size) < 0)
+ return AVERROR(ENOMEM);
+
+ pkt->pts = curtime;
+
+ /* Blit screen grab */
+ if (!BitBlt(s->window_hdc, 0, 0, s->width, s->height,
+ s->source_hdc, s->x_off, s->y_off, SRCCOPY)) {
+ WIN32_API_ERROR("Failed to capture image");
+ return AVERROR(EIO);
+ }
+ if (s->cursor)
+ paint_mouse_pointer(s1, s);
+
+ /* Get bits */
+ if (!GetBitmapBits(s->hbmp, s->size, pkt->data)) {
+ WIN32_API_ERROR("GetBitmapBits failed");
+ return AVERROR(EIO);
+ }
+
+ return s->size;
+}
+
+/**
+ * Closes gdi frame grabber (public device demuxer API).
+ *
+ * @param s1 Context from avformat core
+ * @return 0 success, !0 failure
+ */
+static int gdigrab_read_close(AVFormatContext *s1)
+{
+ gdi_grab *s = s1->priv_data;
+
+ if (s->source_hdc)
+ ReleaseDC(s->window_handle, s->source_hdc);
+ if (s->window_hdc)
+ DeleteDC(s->window_hdc);
+ if (s->hbmp)
+ DeleteObject(s->hbmp);
+ if (s->source_hdc)
+ DeleteDC(s->source_hdc);
+
+ return 0;
+}
+
+/** gdi grabber device demuxer declaration */
+AVInputFormat gdi_grab_device_demuxer =
+{
+ "gdigrab",
+ NULL_IF_CONFIG_SMALL("Frame grabber for Windows using old GDI API"),
+ sizeof(gdi_grab),
+ NULL,
+ gdigrab_read_header,
+ gdigrab_read_packet,
+ gdigrab_read_close,
+ .flags = AVFMT_NOFILE,
+};
--
1.7.1
--------------030602030509020904090203--
More information about the ffmpeg-devel
mailing list