[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