Browse code

Add Win32 GDI-based screen grabbing

Based on original code by Christophe Gisquet in 2010, updated to work
with current ffmpeg APIs.

Supports grabbing a single window or an area of the screen, including
support for multiple monitors (Windows does funky stuff with negative
coordinates here).

I've moved most of the configuration to AVOptions; the input file name
is now only the string "desktop", or "title=<windowname>" to select a
single window. The AVOptions are the same as x11grab where possible.

Code has been added to support a "show_region" mode, like x11grab, which
will draw a rectangle on the screen around the area being captured.

Instead of duplicating code for paletted image handling, I make use of
the GDI API's ability to output DIB (BMP) images, which can be run
through ffmpeg's existing BMP decoder.

Signed-off-by: Calvin Walton <calvin.walton@kepstin.ca>
Signed-off-by: Michael Niedermayer <michaelni@gmx.at>

Calvin Walton authored on 2014/04/03 03:53:10
Showing 7 changed files
... ...
@@ -14,6 +14,7 @@ version <next>:
14 14
 - QTKit input device
15 15
 - improvments to OpenEXR image decoder
16 16
 - support decoding 16-bit RLE SGI images
17
+- GDI screen grabbing for Windows
17 18
 
18 19
 
19 20
 version 2.2:
... ...
@@ -2349,6 +2349,9 @@ dv1394_indev_deps="dv1394"
2349 2349
 dv1394_indev_select="dv_demuxer"
2350 2350
 fbdev_indev_deps="linux_fb_h"
2351 2351
 fbdev_outdev_deps="linux_fb_h"
2352
+gdigrab_indev_deps="CreateDIBSection"
2353
+gdigrab_indev_extralibs="-lgdi32"
2354
+gdigrab_indev_select="bmp_decoder"
2352 2355
 iec61883_indev_deps="libiec61883"
2353 2356
 jack_indev_deps="jack_jack_h sem_timedwait"
2354 2357
 lavfi_indev_deps="avfilter"
... ...
@@ -4741,6 +4744,8 @@ require Xext X11/extensions/XShm.h XShmCreateImage -lXext &&
4741 4741
 require Xfixes X11/extensions/Xfixes.h XFixesGetCursorImage -lXfixes &&
4742 4742
 { enabled xlib || die "ERROR: Xlib not found"; }
4743 4743
 
4744
+check_func_headers "windows.h" CreateDIBSection "$gdigrab_indev_extralibs"
4745
+
4744 4746
 enabled vaapi &&
4745 4747
     check_lib va/va.h vaInitialize -lva ||
4746 4748
     disable vaapi
... ...
@@ -1088,6 +1088,7 @@ performance on systems without hardware floating point support).
1088 1088
 @item Video4Linux2      @tab X      @tab X
1089 1089
 @item VfW capture       @tab X      @tab
1090 1090
 @item X11 grabbing      @tab X      @tab
1091
+@item Win32 grabbing    @tab X      @tab
1091 1092
 @end multitable
1092 1093
 
1093 1094
 @code{X} means that input/output is supported.
... ...
@@ -192,6 +192,81 @@ ffmpeg -f fbdev -frames:v 1 -r 1 -i /dev/fb0 screenshot.jpeg
192 192
 
193 193
 See also @url{http://linux-fbdev.sourceforge.net/}, and fbset(1).
194 194
 
195
+@section gdigrab
196
+
197
+Win32 GDI-based screen capture device.
198
+
199
+This device allows you to capture a region of the display on Windows.
200
+
201
+There are two options for the input filename:
202
+@example
203
+desktop
204
+@end example
205
+or
206
+@example
207
+title=@var{window_title}
208
+@end example
209
+
210
+The first option will capture the entire desktop, or a fixed region of the
211
+desktop. The second option will instead capture the contents of a single
212
+window, regardless of its position on the screen.
213
+
214
+For example, to grab the entire desktop using @command{ffmpeg}:
215
+@example
216
+ffmpeg -f gdigrab -framerate 6 -i desktop out.mpg
217
+@end example
218
+
219
+Grab a 640x480 region at position @code{10,20}:
220
+@example
221
+ffmpeg -f gdigrab -framerate 6 -offset_x 10 -offset_y 20 -video_size vga -i desktop out.mpg
222
+@end example
223
+
224
+Grab the contents of the window named "Calculator"
225
+@example
226
+ffmpeg -f gdigrab -framerate 6 -i title=Calculator out.mpg
227
+@end example
228
+
229
+@subsection Options
230
+
231
+@table @option
232
+@item draw_mouse
233
+Specify whether to draw the mouse pointer. Use the value @code{0} to
234
+not draw the pointer. Default value is @code{1}.
235
+
236
+@item framerate
237
+Set the grabbing frame rate. Default value is @code{ntsc},
238
+corresponding to a frame rate of @code{30000/1001}.
239
+
240
+@item show_region
241
+Show grabbed region on screen.
242
+
243
+If @var{show_region} is specified with @code{1}, then the grabbing
244
+region will be indicated on screen. With this option, it is easy to
245
+know what is being grabbed if only a portion of the screen is grabbed.
246
+
247
+Note that @var{show_region} is incompatible with grabbing the contents
248
+of a single window.
249
+
250
+For example:
251
+@example
252
+ffmpeg -f gdigrab -show_region 1 -framerate 6 -video_size cif -offset_x 10 -offset_y 20 -i desktop out.mpg
253
+@end example
254
+
255
+@item video_size
256
+Set the video frame size. The default is to capture the full screen if @file{desktop} is selected, or the full window size if @file{title=@var{window_title}} is selected.
257
+
258
+@item offset_x
259
+When capturing a region with @var{video_size}, set the distance from the left edge of the screen or desktop.
260
+
261
+Note that the offset calculation is from the top left corner of the primary monitor on Windows. If you have a monitor positioned to the left of your primary monitor, you will need to use a negative @var{offset_x} value to move the region to that monitor.
262
+
263
+@item offset_y
264
+When capturing a region with @var{video_size}, set the distance from the top edge of the screen or desktop.
265
+
266
+Note that the offset calculation is from the top left corner of the primary monitor on Windows. If you have a monitor positioned above your primary monitor, you will need to use a negative @var{offset_y} value to move the region to that monitor.
267
+
268
+@end table
269
+
195 270
 @section iec61883
196 271
 
197 272
 FireWire DV/HDV input device using libiec61883.
... ...
@@ -26,6 +26,7 @@ OBJS-$(CONFIG_FBDEV_INDEV)               += fbdev_dec.o \
26 26
                                             fbdev_common.o
27 27
 OBJS-$(CONFIG_FBDEV_OUTDEV)              += fbdev_enc.o \
28 28
                                             fbdev_common.o
29
+OBJS-$(CONFIG_GDIGRAB_INDEV)             += gdigrab.o
29 30
 OBJS-$(CONFIG_IEC61883_INDEV)            += iec61883.o
30 31
 OBJS-$(CONFIG_JACK_INDEV)                += jack_audio.o timefilter.o
31 32
 OBJS-$(CONFIG_LAVFI_INDEV)               += lavfi.o
... ...
@@ -53,6 +53,7 @@ void avdevice_register_all(void)
53 53
     REGISTER_INDEV   (DSHOW,            dshow);
54 54
     REGISTER_INDEV   (DV1394,           dv1394);
55 55
     REGISTER_INOUTDEV(FBDEV,            fbdev);
56
+    REGISTER_INDEV   (GDIGRAB,          gdigrab);
56 57
     REGISTER_INDEV   (IEC61883,         iec61883);
57 58
     REGISTER_INDEV   (JACK,             jack);
58 59
     REGISTER_INDEV   (LAVFI,            lavfi);
59 60
new file mode 100644
... ...
@@ -0,0 +1,630 @@
0
+/*
1
+ * GDI video grab interface
2
+ *
3
+ * This file is part of FFmpeg.
4
+ *
5
+ * Copyright (C) 2013 Calvin Walton <calvin.walton@kepstin.ca>
6
+ * Copyright (C) 2007-2010 Christophe Gisquet <word1.word2@gmail.com>
7
+ *
8
+ * FFmpeg is free software; you can redistribute it and/or
9
+ * modify it under the terms of the GNU Lesser General Public License
10
+ * as published by the Free Software Foundation; either version 2.1
11
+ * of the License, or (at your option) any later version.
12
+ *
13
+ * FFmpeg is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU Lesser General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU Lesser General Public
19
+ * License along with FFmpeg; if not, write to the Free Software
20
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
+ */
22
+
23
+/**
24
+ * @file
25
+ * GDI frame device demuxer
26
+ * @author Calvin Walton <calvin.walton@kepstin.ca>
27
+ * @author Christophe Gisquet <word1.word2@gmail.com>
28
+ */
29
+
30
+#include "config.h"
31
+#include "libavformat/internal.h"
32
+#include "libavutil/opt.h"
33
+#include "libavutil/time.h"
34
+#include <windows.h>
35
+
36
+/**
37
+ * GDI Device Demuxer context
38
+ */
39
+struct gdigrab {
40
+    const AVClass *class;   /**< Class for private options */
41
+
42
+    int        frame_size;  /**< Size in bytes of the frame pixel data */
43
+    int        header_size; /**< Size in bytes of the DIB header */
44
+    AVRational time_base;   /**< Time base */
45
+    int64_t    time_frame;  /**< Current time */
46
+
47
+    int        draw_mouse;  /**< Draw mouse cursor (private option) */
48
+    int        show_region; /**< Draw border (private option) */
49
+    AVRational framerate;   /**< Capture framerate (private option) */
50
+    int        width;       /**< Width of the grab frame (private option) */
51
+    int        height;      /**< Height of the grab frame (private option) */
52
+    int        offset_x;    /**< Capture x offset (private option) */
53
+    int        offset_y;    /**< Capture y offset (private option) */
54
+
55
+    HWND       hwnd;        /**< Handle of the window for the grab */
56
+    HDC        source_hdc;  /**< Source device context */
57
+    HDC        dest_hdc;    /**< Destination, source-compatible DC */
58
+    BITMAPINFO bmi;         /**< Information describing DIB format */
59
+    HBITMAP    hbmp;        /**< Information on the bitmap captured */
60
+    void      *buffer;      /**< The buffer containing the bitmap image data */
61
+    RECT       clip_rect;   /**< The subarea of the screen or window to clip */
62
+
63
+    HWND       region_hwnd; /**< Handle of the region border window */
64
+
65
+    int cursor_error_printed;
66
+};
67
+
68
+#define WIN32_API_ERROR(str)                                            \
69
+    av_log(s1, AV_LOG_ERROR, str " (error %li)\n", GetLastError())
70
+
71
+#define REGION_WND_BORDER 3
72
+
73
+/**
74
+ * Callback to handle Windows messages for the region outline window.
75
+ *
76
+ * In particular, this handles painting the frame rectangle.
77
+ *
78
+ * @param hwnd The region outline window handle.
79
+ * @param msg The Windows message.
80
+ * @param wparam First Windows message parameter.
81
+ * @param lparam Second Windows message parameter.
82
+ * @return 0 success, !0 failure
83
+ */
84
+static LRESULT CALLBACK
85
+gdigrab_region_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
86
+{
87
+    PAINTSTRUCT ps;
88
+    HDC hdc;
89
+    RECT rect;
90
+
91
+    switch (msg) {
92
+    case WM_PAINT:
93
+        hdc = BeginPaint(hwnd, &ps);
94
+
95
+        GetClientRect(hwnd, &rect);
96
+        FrameRect(hdc, &rect, GetStockObject(BLACK_BRUSH));
97
+
98
+        rect.left++; rect.top++; rect.right--; rect.bottom--;
99
+        FrameRect(hdc, &rect, GetStockObject(WHITE_BRUSH));
100
+
101
+        rect.left++; rect.top++; rect.right--; rect.bottom--;
102
+        FrameRect(hdc, &rect, GetStockObject(BLACK_BRUSH));
103
+
104
+        EndPaint(hwnd, &ps);
105
+        break;
106
+    default:
107
+        return DefWindowProc(hwnd, msg, wparam, lparam);
108
+    }
109
+    return 0;
110
+}
111
+
112
+/**
113
+ * Initialize the region outline window.
114
+ *
115
+ * @param s1 The format context.
116
+ * @param gdigrab gdigrab context.
117
+ * @return 0 success, !0 failure
118
+ */
119
+static int
120
+gdigrab_region_wnd_init(AVFormatContext *s1, struct gdigrab *gdigrab)
121
+{
122
+    HWND hwnd;
123
+    RECT rect = gdigrab->clip_rect;
124
+    HRGN region = NULL;
125
+    HRGN region_interior = NULL;
126
+
127
+    DWORD style = WS_POPUP | WS_VISIBLE;
128
+    DWORD ex = WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_TRANSPARENT;
129
+
130
+    rect.left -= REGION_WND_BORDER; rect.top -= REGION_WND_BORDER;
131
+    rect.right += REGION_WND_BORDER; rect.bottom += REGION_WND_BORDER;
132
+
133
+    AdjustWindowRectEx(&rect, style, FALSE, ex);
134
+
135
+    // Create a window with no owner; use WC_DIALOG instead of writing a custom
136
+    // window class
137
+    hwnd = CreateWindowEx(ex, WC_DIALOG, NULL, style, rect.left, rect.top,
138
+            rect.right - rect.left, rect.bottom - rect.top,
139
+            NULL, NULL, NULL, NULL);
140
+    if (!hwnd) {
141
+        WIN32_API_ERROR("Could not create region display window");
142
+        goto error;
143
+    }
144
+
145
+    // Set the window shape to only include the border area
146
+    GetClientRect(hwnd, &rect);
147
+    region = CreateRectRgn(0, 0,
148
+            rect.right - rect.left, rect.bottom - rect.top);
149
+    region_interior = CreateRectRgn(REGION_WND_BORDER, REGION_WND_BORDER,
150
+            rect.right - rect.left - REGION_WND_BORDER,
151
+            rect.bottom - rect.top - REGION_WND_BORDER);
152
+    CombineRgn(region, region, region_interior, RGN_DIFF);
153
+    if (!SetWindowRgn(hwnd, region, FALSE)) {
154
+        WIN32_API_ERROR("Could not set window region");
155
+        goto error;
156
+    }
157
+    // The "region" memory is now owned by the window
158
+    region = NULL;
159
+    DeleteObject(region_interior);
160
+
161
+    SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR) gdigrab_region_wnd_proc);
162
+
163
+    ShowWindow(hwnd, SW_SHOW);
164
+
165
+    gdigrab->region_hwnd = hwnd;
166
+
167
+    return 0;
168
+
169
+error:
170
+    if (region)
171
+        DeleteObject(region);
172
+    if (region_interior)
173
+        DeleteObject(region_interior);
174
+    if (hwnd)
175
+        DestroyWindow(hwnd);
176
+    return 1;
177
+}
178
+
179
+/**
180
+ * Cleanup/free the region outline window.
181
+ *
182
+ * @param s1 The format context.
183
+ * @param gdigrab gdigrab context.
184
+ */
185
+static void
186
+gdigrab_region_wnd_destroy(AVFormatContext *s1, struct gdigrab *gdigrab)
187
+{
188
+    if (gdigrab->region_hwnd)
189
+        DestroyWindow(gdigrab->region_hwnd);
190
+    gdigrab->region_hwnd = NULL;
191
+}
192
+
193
+/**
194
+ * Process the Windows message queue.
195
+ *
196
+ * This is important to prevent Windows from thinking the window has become
197
+ * unresponsive. As well, things like WM_PAINT (to actually draw the window
198
+ * contents) are handled from the message queue context.
199
+ *
200
+ * @param s1 The format context.
201
+ * @param gdigrab gdigrab context.
202
+ */
203
+static void
204
+gdigrab_region_wnd_update(AVFormatContext *s1, struct gdigrab *gdigrab)
205
+{
206
+    HWND hwnd = gdigrab->region_hwnd;
207
+    MSG msg;
208
+
209
+    while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)) {
210
+        DispatchMessage(&msg);
211
+    }
212
+}
213
+
214
+/**
215
+ * Initializes the gdi grab device demuxer (public device demuxer API).
216
+ *
217
+ * @param s1 Context from avformat core
218
+ * @return AVERROR_IO error, 0 success
219
+ */
220
+static int
221
+gdigrab_read_header(AVFormatContext *s1)
222
+{
223
+    struct gdigrab *gdigrab = s1->priv_data;
224
+
225
+    HWND hwnd;
226
+    HDC source_hdc = NULL;
227
+    HDC dest_hdc   = NULL;
228
+    BITMAPINFO bmi;
229
+    HBITMAP hbmp   = NULL;
230
+    void *buffer   = NULL;
231
+
232
+    const char *filename = s1->filename;
233
+    const char *name     = NULL;
234
+    AVStream   *st       = NULL;
235
+
236
+    int bpp;
237
+    RECT virtual_rect;
238
+    RECT clip_rect;
239
+    BITMAP bmp;
240
+    int ret;
241
+
242
+    if (!strncmp(filename, "title=", 6)) {
243
+        name = filename + 6;
244
+        hwnd = FindWindow(NULL, name);
245
+        if (!hwnd) {
246
+            av_log(s1, AV_LOG_ERROR,
247
+                   "Can't find window '%s', aborting.\n", name);
248
+            ret = AVERROR(EIO);
249
+            goto error;
250
+        }
251
+        if (gdigrab->show_region) {
252
+            av_log(s1, AV_LOG_WARNING,
253
+                    "Can't show region when grabbing a window.\n");
254
+            gdigrab->show_region = 0;
255
+        }
256
+    } else if (!strcmp(filename, "desktop")) {
257
+        hwnd = NULL;
258
+    } else {
259
+        av_log(s1, AV_LOG_ERROR,
260
+               "Please use \"desktop\" or \"title=<windowname>\" to specify your target.\n");
261
+        ret = AVERROR(EIO);
262
+        goto error;
263
+    }
264
+
265
+    if (hwnd) {
266
+        GetClientRect(hwnd, &virtual_rect);
267
+    } else {
268
+        virtual_rect.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
269
+        virtual_rect.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
270
+        virtual_rect.right = virtual_rect.left + GetSystemMetrics(SM_CXVIRTUALSCREEN);
271
+        virtual_rect.bottom = virtual_rect.top + GetSystemMetrics(SM_CYVIRTUALSCREEN);
272
+    }
273
+
274
+    /* If no width or height set, use full screen/window area */
275
+    if (!gdigrab->width || !gdigrab->height) {
276
+        clip_rect.left = virtual_rect.left;
277
+        clip_rect.top = virtual_rect.top;
278
+        clip_rect.right = virtual_rect.right;
279
+        clip_rect.bottom = virtual_rect.bottom;
280
+    } else {
281
+        clip_rect.left = gdigrab->offset_x;
282
+        clip_rect.top = gdigrab->offset_y;
283
+        clip_rect.right = gdigrab->width + gdigrab->offset_x;
284
+        clip_rect.bottom = gdigrab->height + gdigrab->offset_y;
285
+    }
286
+
287
+    if (clip_rect.left < virtual_rect.left ||
288
+            clip_rect.top < virtual_rect.top ||
289
+            clip_rect.right > virtual_rect.right ||
290
+            clip_rect.bottom > virtual_rect.bottom) {
291
+            av_log(s1, AV_LOG_ERROR,
292
+                    "Capture area (%li,%li),(%li,%li) extends outside window area (%li,%li),(%li,%li)",
293
+                    clip_rect.left, clip_rect.top,
294
+                    clip_rect.right, clip_rect.bottom,
295
+                    virtual_rect.left, virtual_rect.top,
296
+                    virtual_rect.right, virtual_rect.bottom);
297
+            ret = AVERROR(EIO);
298
+            goto error;
299
+    }
300
+
301
+    /* This will get the device context for the selected window, or if
302
+     * none, the primary screen */
303
+    source_hdc = GetDC(hwnd);
304
+    if (!source_hdc) {
305
+        WIN32_API_ERROR("Couldn't get window device context");
306
+        ret = AVERROR(EIO);
307
+        goto error;
308
+    }
309
+    bpp = GetDeviceCaps(source_hdc, BITSPIXEL);
310
+
311
+    if (name) {
312
+        av_log(s1, AV_LOG_INFO,
313
+               "Found window %s, capturing %lix%lix%i at (%li,%li)\n",
314
+               name,
315
+               clip_rect.right - clip_rect.left,
316
+               clip_rect.bottom - clip_rect.top,
317
+               bpp, clip_rect.left, clip_rect.top);
318
+    } else {
319
+        av_log(s1, AV_LOG_INFO,
320
+               "Capturing whole desktop as %lix%lix%i at (%li,%li)\n",
321
+               clip_rect.right - clip_rect.left,
322
+               clip_rect.bottom - clip_rect.top,
323
+               bpp, clip_rect.left, clip_rect.top);
324
+    }
325
+
326
+    if (clip_rect.right - clip_rect.left <= 0 ||
327
+            clip_rect.bottom - clip_rect.top <= 0 || bpp%8) {
328
+        av_log(s1, AV_LOG_ERROR, "Invalid properties, aborting\n");
329
+        ret = AVERROR(EIO);
330
+        goto error;
331
+    }
332
+
333
+    dest_hdc = CreateCompatibleDC(source_hdc);
334
+    if (!dest_hdc) {
335
+        WIN32_API_ERROR("Screen DC CreateCompatibleDC");
336
+        ret = AVERROR(EIO);
337
+        goto error;
338
+    }
339
+
340
+    /* Create a DIB and select it into the dest_hdc */
341
+    bmi.bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
342
+    bmi.bmiHeader.biWidth         = clip_rect.right - clip_rect.left;
343
+    bmi.bmiHeader.biHeight        = -(clip_rect.bottom - clip_rect.top);
344
+    bmi.bmiHeader.biPlanes        = 1;
345
+    bmi.bmiHeader.biBitCount      = bpp;
346
+    bmi.bmiHeader.biCompression   = BI_RGB;
347
+    bmi.bmiHeader.biSizeImage     = 0;
348
+    bmi.bmiHeader.biXPelsPerMeter = 0;
349
+    bmi.bmiHeader.biYPelsPerMeter = 0;
350
+    bmi.bmiHeader.biClrUsed       = 0;
351
+    bmi.bmiHeader.biClrImportant  = 0;
352
+    hbmp = CreateDIBSection(dest_hdc, &bmi, DIB_RGB_COLORS,
353
+            &buffer, NULL, 0);
354
+    if (!hbmp) {
355
+        WIN32_API_ERROR("Creating DIB Section");
356
+        ret = AVERROR(EIO);
357
+        goto error;
358
+    }
359
+
360
+    if (!SelectObject(dest_hdc, hbmp)) {
361
+        WIN32_API_ERROR("SelectObject");
362
+        ret = AVERROR(EIO);
363
+        goto error;
364
+    }
365
+
366
+    /* Get info from the bitmap */
367
+    GetObject(hbmp, sizeof(BITMAP), &bmp);
368
+
369
+    st = avformat_new_stream(s1, NULL);
370
+    if (!st) {
371
+        ret = AVERROR(ENOMEM);
372
+        goto error;
373
+    }
374
+    avpriv_set_pts_info(st, 64, 1, 1000000); /* 64 bits pts in us */
375
+
376
+    gdigrab->frame_size  = bmp.bmWidthBytes * bmp.bmHeight * bmp.bmPlanes;
377
+    gdigrab->header_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) +
378
+                           (bpp <= 8 ? (1 << bpp) : 0) * sizeof(RGBQUAD) /* palette size */;
379
+    gdigrab->time_base   = av_inv_q(gdigrab->framerate);
380
+    gdigrab->time_frame  = av_gettime() / av_q2d(gdigrab->time_base);
381
+
382
+    gdigrab->hwnd       = hwnd;
383
+    gdigrab->source_hdc = source_hdc;
384
+    gdigrab->dest_hdc   = dest_hdc;
385
+    gdigrab->hbmp       = hbmp;
386
+    gdigrab->bmi        = bmi;
387
+    gdigrab->buffer     = buffer;
388
+    gdigrab->clip_rect  = clip_rect;
389
+
390
+    gdigrab->cursor_error_printed = 0;
391
+
392
+    if (gdigrab->show_region) {
393
+        if (gdigrab_region_wnd_init(s1, gdigrab)) {
394
+            ret = AVERROR(EIO);
395
+            goto error;
396
+        }
397
+    }
398
+
399
+    st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
400
+    st->codec->codec_id   = AV_CODEC_ID_BMP;
401
+    st->codec->time_base  = gdigrab->time_base;
402
+    st->codec->bit_rate   = (gdigrab->header_size + gdigrab->frame_size) * 1/av_q2d(gdigrab->time_base) * 8;
403
+
404
+    return 0;
405
+
406
+error:
407
+    if (source_hdc)
408
+        ReleaseDC(hwnd, source_hdc);
409
+    if (dest_hdc)
410
+        DeleteDC(dest_hdc);
411
+    if (hbmp)
412
+        DeleteObject(hbmp);
413
+    if (source_hdc)
414
+        DeleteDC(source_hdc);
415
+    return ret;
416
+}
417
+
418
+/**
419
+ * Paints a mouse pointer in a Win32 image.
420
+ *
421
+ * @param s1 Context of the log information
422
+ * @param s  Current grad structure
423
+ */
424
+static void paint_mouse_pointer(AVFormatContext *s1, struct gdigrab *gdigrab)
425
+{
426
+    CURSORINFO ci = {0};
427
+
428
+#define CURSOR_ERROR(str)                 \
429
+    if (!gdigrab->cursor_error_printed) {       \
430
+        WIN32_API_ERROR(str);             \
431
+        gdigrab->cursor_error_printed = 1;      \
432
+    }
433
+
434
+    ci.cbSize = sizeof(ci);
435
+
436
+    if (GetCursorInfo(&ci)) {
437
+        HCURSOR icon = CopyCursor(ci.hCursor);
438
+        ICONINFO info;
439
+        POINT pos;
440
+        RECT clip_rect = gdigrab->clip_rect;
441
+        HWND hwnd = gdigrab->hwnd;
442
+
443
+        if (ci.flags != CURSOR_SHOWING)
444
+            return;
445
+
446
+        if (!icon) {
447
+            /* Use the standard arrow cursor as a fallback.
448
+             * You'll probably only hit this in Wine, which can't fetch
449
+             * the current system cursor. */
450
+            icon = CopyCursor(LoadCursor(NULL, IDC_ARROW));
451
+        }
452
+
453
+        if (!GetIconInfo(icon, &info)) {
454
+            CURSOR_ERROR("Could not get icon info");
455
+            goto icon_error;
456
+        }
457
+
458
+        pos.x = ci.ptScreenPos.x - clip_rect.left - info.xHotspot;
459
+        pos.y = ci.ptScreenPos.y - clip_rect.top - info.yHotspot;
460
+
461
+        if (hwnd) {
462
+            RECT rect;
463
+
464
+            if (GetWindowRect(hwnd, &rect)) {
465
+                pos.x -= rect.left;
466
+                pos.y -= rect.top;
467
+            } else {
468
+                CURSOR_ERROR("Couldn't get window rectangle");
469
+                goto icon_error;
470
+            }
471
+        }
472
+
473
+        av_log(s1, AV_LOG_DEBUG, "Cursor pos (%li,%li) -> (%li,%li)\n",
474
+                ci.ptScreenPos.x, ci.ptScreenPos.y, pos.x, pos.y);
475
+
476
+        if (pos.x >= 0 && pos.x <= clip_rect.right - clip_rect.left &&
477
+                pos.y >= 0 && pos.y <= clip_rect.bottom - clip_rect.top) {
478
+            if (!DrawIcon(gdigrab->dest_hdc, pos.x, pos.y, icon))
479
+                CURSOR_ERROR("Couldn't draw icon");
480
+        }
481
+
482
+icon_error:
483
+        if (icon)
484
+            DestroyCursor(icon);
485
+    } else {
486
+        CURSOR_ERROR("Couldn't get cursor info");
487
+    }
488
+}
489
+
490
+/**
491
+ * Grabs a frame from gdi (public device demuxer API).
492
+ *
493
+ * @param s1 Context from avformat core
494
+ * @param pkt Packet holding the grabbed frame
495
+ * @return frame size in bytes
496
+ */
497
+static int gdigrab_read_packet(AVFormatContext *s1, AVPacket *pkt)
498
+{
499
+    struct gdigrab *gdigrab = s1->priv_data;
500
+
501
+    HDC        dest_hdc   = gdigrab->dest_hdc;
502
+    HDC        source_hdc = gdigrab->source_hdc;
503
+    RECT       clip_rect  = gdigrab->clip_rect;
504
+    AVRational time_base  = gdigrab->time_base;
505
+    int64_t    time_frame = gdigrab->time_frame;
506
+
507
+    BITMAPFILEHEADER bfh;
508
+    int file_size = gdigrab->header_size + gdigrab->frame_size;
509
+
510
+    int64_t curtime, delay;
511
+
512
+    /* Calculate the time of the next frame */
513
+    time_frame += INT64_C(1000000);
514
+
515
+    /* Run Window message processing queue */
516
+    if (gdigrab->show_region)
517
+        gdigrab_region_wnd_update(s1, gdigrab);
518
+
519
+    /* wait based on the frame rate */
520
+    for (;;) {
521
+        curtime = av_gettime();
522
+        delay = time_frame * av_q2d(time_base) - curtime;
523
+        if (delay <= 0) {
524
+            if (delay < INT64_C(-1000000) * av_q2d(time_base)) {
525
+                time_frame += INT64_C(1000000);
526
+            }
527
+            break;
528
+        }
529
+        if (s1->flags & AVFMT_FLAG_NONBLOCK) {
530
+            return AVERROR(EAGAIN);
531
+        } else {
532
+            av_usleep(delay);
533
+        }
534
+    }
535
+
536
+    if (av_new_packet(pkt, file_size) < 0)
537
+        return AVERROR(ENOMEM);
538
+    pkt->pts = curtime;
539
+
540
+    /* Blit screen grab */
541
+    if (!BitBlt(dest_hdc, 0, 0,
542
+                clip_rect.right - clip_rect.left,
543
+                clip_rect.bottom - clip_rect.top,
544
+                source_hdc,
545
+                clip_rect.left, clip_rect.top, SRCCOPY | CAPTUREBLT)) {
546
+        WIN32_API_ERROR("Failed to capture image");
547
+        return AVERROR(EIO);
548
+    }
549
+    if (gdigrab->draw_mouse)
550
+        paint_mouse_pointer(s1, gdigrab);
551
+
552
+    /* Copy bits to packet data */
553
+
554
+    bfh.bfType = 0x4d42; /* "BM" in little-endian */
555
+    bfh.bfSize = file_size;
556
+    bfh.bfReserved1 = 0;
557
+    bfh.bfReserved2 = 0;
558
+    bfh.bfOffBits = gdigrab->header_size;
559
+
560
+    memcpy(pkt->data, &bfh, sizeof(bfh));
561
+
562
+    memcpy(pkt->data + sizeof(bfh), &gdigrab->bmi.bmiHeader, sizeof(gdigrab->bmi.bmiHeader));
563
+
564
+    if (gdigrab->bmi.bmiHeader.biBitCount <= 8)
565
+        GetDIBColorTable(dest_hdc, 0, 1 << gdigrab->bmi.bmiHeader.biBitCount,
566
+                (RGBQUAD *) (pkt->data + sizeof(bfh) + sizeof(gdigrab->bmi.bmiHeader)));
567
+
568
+    memcpy(pkt->data + gdigrab->header_size, gdigrab->buffer, gdigrab->frame_size);
569
+
570
+    gdigrab->time_frame = time_frame;
571
+
572
+    return gdigrab->header_size + gdigrab->frame_size;
573
+}
574
+
575
+/**
576
+ * Closes gdi frame grabber (public device demuxer API).
577
+ *
578
+ * @param s1 Context from avformat core
579
+ * @return 0 success, !0 failure
580
+ */
581
+static int gdigrab_read_close(AVFormatContext *s1)
582
+{
583
+    struct gdigrab *s = s1->priv_data;
584
+
585
+    if (s->show_region)
586
+        gdigrab_region_wnd_destroy(s1, s);
587
+
588
+    if (s->source_hdc)
589
+        ReleaseDC(s->hwnd, s->source_hdc);
590
+    if (s->dest_hdc)
591
+        DeleteDC(s->dest_hdc);
592
+    if (s->hbmp)
593
+        DeleteObject(s->hbmp);
594
+    if (s->source_hdc)
595
+        DeleteDC(s->source_hdc);
596
+
597
+    return 0;
598
+}
599
+
600
+#define OFFSET(x) offsetof(struct gdigrab, x)
601
+#define DEC AV_OPT_FLAG_DECODING_PARAM
602
+static const AVOption options[] = {
603
+    { "draw_mouse", "draw the mouse pointer", OFFSET(draw_mouse), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, DEC },
604
+    { "show_region", "draw border around capture area", OFFSET(show_region), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, DEC },
605
+    { "framerate", "set video frame rate", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, {.str = "ntsc"}, 0, 0, DEC },
606
+    { "video_size", "set video frame size", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, 0, 0, DEC },
607
+    { "offset_x", "capture area x offset", OFFSET(offset_x), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC },
608
+    { "offset_y", "capture area y offset", OFFSET(offset_y), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC },
609
+    { NULL },
610
+};
611
+
612
+static const AVClass gdigrab_class = {
613
+    .class_name = "GDIgrab indev",
614
+    .item_name  = av_default_item_name,
615
+    .option     = options,
616
+    .version    = LIBAVUTIL_VERSION_INT,
617
+};
618
+
619
+/** gdi grabber device demuxer declaration */
620
+AVInputFormat ff_gdigrab_demuxer = {
621
+    .name           = "gdigrab",
622
+    .long_name      = NULL_IF_CONFIG_SMALL("GDI API Windows frame grabber"),
623
+    .priv_data_size = sizeof(struct gdigrab),
624
+    .read_header    = gdigrab_read_header,
625
+    .read_packet    = gdigrab_read_packet,
626
+    .read_close     = gdigrab_read_close,
627
+    .flags          = AVFMT_NOFILE,
628
+    .priv_class     = &gdigrab_class,
629
+};