2 * GDI video grab interface
4 * This file is part of FFmpeg.
6 * Copyright (C) 2013 Calvin Walton <calvin.walton@kepstin.ca>
7 * Copyright (C) 2007-2010 Christophe Gisquet <word1.word2@gmail.com>
9 * FFmpeg is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * FFmpeg is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with FFmpeg; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
26 * GDI frame device demuxer
27 * @author Calvin Walton <calvin.walton@kepstin.ca>
28 * @author Christophe Gisquet <word1.word2@gmail.com>
32 #include "libavformat/internal.h"
33 #include "libavutil/opt.h"
34 #include "libavutil/time.h"
38 * GDI Device Demuxer context
41 const AVClass
*class; /**< Class for private options */
43 int frame_size
; /**< Size in bytes of the frame pixel data */
44 int header_size
; /**< Size in bytes of the DIB header */
45 AVRational time_base
; /**< Time base */
46 int64_t time_frame
; /**< Current time */
48 int draw_mouse
; /**< Draw mouse cursor (private option) */
49 int show_region
; /**< Draw border (private option) */
50 AVRational framerate
; /**< Capture framerate (private option) */
51 int width
; /**< Width of the grab frame (private option) */
52 int height
; /**< Height of the grab frame (private option) */
53 int offset_x
; /**< Capture x offset (private option) */
54 int offset_y
; /**< Capture y offset (private option) */
56 HWND hwnd
; /**< Handle of the window for the grab */
57 HDC source_hdc
; /**< Source device context */
58 HDC dest_hdc
; /**< Destination, source-compatible DC */
59 BITMAPINFO bmi
; /**< Information describing DIB format */
60 HBITMAP hbmp
; /**< Information on the bitmap captured */
61 void *buffer
; /**< The buffer containing the bitmap image data */
62 RECT clip_rect
; /**< The subarea of the screen or window to clip */
64 HWND region_hwnd
; /**< Handle of the region border window */
66 int cursor_error_printed
;
69 #define WIN32_API_ERROR(str) \
70 av_log(s1, AV_LOG_ERROR, str " (error %li)\n", GetLastError())
72 #define REGION_WND_BORDER 3
75 * Callback to handle Windows messages for the region outline window.
77 * In particular, this handles painting the frame rectangle.
79 * @param hwnd The region outline window handle.
80 * @param msg The Windows message.
81 * @param wparam First Windows message parameter.
82 * @param lparam Second Windows message parameter.
83 * @return 0 success, !0 failure
85 static LRESULT CALLBACK
86 gdigrab_region_wnd_proc(HWND hwnd
, UINT msg
, WPARAM wparam
, LPARAM lparam
)
94 hdc
= BeginPaint(hwnd
, &ps
);
96 GetClientRect(hwnd
, &rect
);
97 FrameRect(hdc
, &rect
, GetStockObject(BLACK_BRUSH
));
99 rect
.left
++; rect
.top
++; rect
.right
--; rect
.bottom
--;
100 FrameRect(hdc
, &rect
, GetStockObject(WHITE_BRUSH
));
102 rect
.left
++; rect
.top
++; rect
.right
--; rect
.bottom
--;
103 FrameRect(hdc
, &rect
, GetStockObject(BLACK_BRUSH
));
108 return DefWindowProc(hwnd
, msg
, wparam
, lparam
);
114 * Initialize the region outline window.
116 * @param s1 The format context.
117 * @param gdigrab gdigrab context.
118 * @return 0 success, !0 failure
121 gdigrab_region_wnd_init(AVFormatContext
*s1
, struct gdigrab
*gdigrab
)
124 RECT rect
= gdigrab
->clip_rect
;
126 HRGN region_interior
= NULL
;
128 DWORD style
= WS_POPUP
| WS_VISIBLE
;
129 DWORD ex
= WS_EX_TOOLWINDOW
| WS_EX_TOPMOST
| WS_EX_TRANSPARENT
;
131 rect
.left
-= REGION_WND_BORDER
; rect
.top
-= REGION_WND_BORDER
;
132 rect
.right
+= REGION_WND_BORDER
; rect
.bottom
+= REGION_WND_BORDER
;
134 AdjustWindowRectEx(&rect
, style
, FALSE
, ex
);
136 // Create a window with no owner; use WC_DIALOG instead of writing a custom
138 hwnd
= CreateWindowEx(ex
, WC_DIALOG
, NULL
, style
, rect
.left
, rect
.top
,
139 rect
.right
- rect
.left
, rect
.bottom
- rect
.top
,
140 NULL
, NULL
, NULL
, NULL
);
142 WIN32_API_ERROR("Could not create region display window");
146 // Set the window shape to only include the border area
147 GetClientRect(hwnd
, &rect
);
148 region
= CreateRectRgn(0, 0,
149 rect
.right
- rect
.left
, rect
.bottom
- rect
.top
);
150 region_interior
= CreateRectRgn(REGION_WND_BORDER
, REGION_WND_BORDER
,
151 rect
.right
- rect
.left
- REGION_WND_BORDER
,
152 rect
.bottom
- rect
.top
- REGION_WND_BORDER
);
153 CombineRgn(region
, region
, region_interior
, RGN_DIFF
);
154 if (!SetWindowRgn(hwnd
, region
, FALSE
)) {
155 WIN32_API_ERROR("Could not set window region");
158 // The "region" memory is now owned by the window
160 DeleteObject(region_interior
);
162 SetWindowLongPtr(hwnd
, GWLP_WNDPROC
, (LONG_PTR
) gdigrab_region_wnd_proc
);
164 ShowWindow(hwnd
, SW_SHOW
);
166 gdigrab
->region_hwnd
= hwnd
;
172 DeleteObject(region
);
174 DeleteObject(region_interior
);
181 * Cleanup/free the region outline window.
183 * @param s1 The format context.
184 * @param gdigrab gdigrab context.
187 gdigrab_region_wnd_destroy(AVFormatContext
*s1
, struct gdigrab
*gdigrab
)
189 if (gdigrab
->region_hwnd
)
190 DestroyWindow(gdigrab
->region_hwnd
);
191 gdigrab
->region_hwnd
= NULL
;
195 * Process the Windows message queue.
197 * This is important to prevent Windows from thinking the window has become
198 * unresponsive. As well, things like WM_PAINT (to actually draw the window
199 * contents) are handled from the message queue context.
201 * @param s1 The format context.
202 * @param gdigrab gdigrab context.
205 gdigrab_region_wnd_update(AVFormatContext
*s1
, struct gdigrab
*gdigrab
)
207 HWND hwnd
= gdigrab
->region_hwnd
;
210 while (PeekMessage(&msg
, hwnd
, 0, 0, PM_REMOVE
)) {
211 DispatchMessage(&msg
);
216 * Initializes the gdi grab device demuxer (public device demuxer API).
218 * @param s1 Context from avformat core
219 * @return AVERROR_IO error, 0 success
222 gdigrab_read_header(AVFormatContext
*s1
)
224 struct gdigrab
*gdigrab
= s1
->priv_data
;
227 HDC source_hdc
= NULL
;
233 const char *filename
= s1
->filename
;
234 const char *name
= NULL
;
243 if (!strncmp(filename
, "title=", 6)) {
245 hwnd
= FindWindow(NULL
, name
);
247 av_log(s1
, AV_LOG_ERROR
,
248 "Can't find window '%s', aborting.\n", name
);
252 if (gdigrab
->show_region
) {
253 av_log(s1
, AV_LOG_WARNING
,
254 "Can't show region when grabbing a window.\n");
255 gdigrab
->show_region
= 0;
257 } else if (!strcmp(filename
, "desktop")) {
260 av_log(s1
, AV_LOG_ERROR
,
261 "Please use \"desktop\" or \"title=<windowname>\" to specify your target.\n");
267 GetClientRect(hwnd
, &virtual_rect
);
269 virtual_rect
.left
= GetSystemMetrics(SM_XVIRTUALSCREEN
);
270 virtual_rect
.top
= GetSystemMetrics(SM_YVIRTUALSCREEN
);
271 virtual_rect
.right
= virtual_rect
.left
+ GetSystemMetrics(SM_CXVIRTUALSCREEN
);
272 virtual_rect
.bottom
= virtual_rect
.top
+ GetSystemMetrics(SM_CYVIRTUALSCREEN
);
275 /* If no width or height set, use full screen/window area */
276 if (!gdigrab
->width
|| !gdigrab
->height
) {
277 clip_rect
.left
= virtual_rect
.left
;
278 clip_rect
.top
= virtual_rect
.top
;
279 clip_rect
.right
= virtual_rect
.right
;
280 clip_rect
.bottom
= virtual_rect
.bottom
;
282 clip_rect
.left
= gdigrab
->offset_x
;
283 clip_rect
.top
= gdigrab
->offset_y
;
284 clip_rect
.right
= gdigrab
->width
+ gdigrab
->offset_x
;
285 clip_rect
.bottom
= gdigrab
->height
+ gdigrab
->offset_y
;
288 if (clip_rect
.left
< virtual_rect
.left
||
289 clip_rect
.top
< virtual_rect
.top
||
290 clip_rect
.right
> virtual_rect
.right
||
291 clip_rect
.bottom
> virtual_rect
.bottom
) {
292 av_log(s1
, AV_LOG_ERROR
,
293 "Capture area (%li,%li),(%li,%li) extends outside window area (%li,%li),(%li,%li)",
294 clip_rect
.left
, clip_rect
.top
,
295 clip_rect
.right
, clip_rect
.bottom
,
296 virtual_rect
.left
, virtual_rect
.top
,
297 virtual_rect
.right
, virtual_rect
.bottom
);
302 /* This will get the device context for the selected window, or if
303 * none, the primary screen */
304 source_hdc
= GetDC(hwnd
);
306 WIN32_API_ERROR("Couldn't get window device context");
310 bpp
= GetDeviceCaps(source_hdc
, BITSPIXEL
);
313 av_log(s1
, AV_LOG_INFO
,
314 "Found window %s, capturing %lix%lix%i at (%li,%li)\n",
316 clip_rect
.right
- clip_rect
.left
,
317 clip_rect
.bottom
- clip_rect
.top
,
318 bpp
, clip_rect
.left
, clip_rect
.top
);
320 av_log(s1
, AV_LOG_INFO
,
321 "Capturing whole desktop as %lix%lix%i at (%li,%li)\n",
322 clip_rect
.right
- clip_rect
.left
,
323 clip_rect
.bottom
- clip_rect
.top
,
324 bpp
, clip_rect
.left
, clip_rect
.top
);
327 if (clip_rect
.right
- clip_rect
.left
<= 0 ||
328 clip_rect
.bottom
- clip_rect
.top
<= 0 || bpp
%8) {
329 av_log(s1
, AV_LOG_ERROR
, "Invalid properties, aborting\n");
334 dest_hdc
= CreateCompatibleDC(source_hdc
);
336 WIN32_API_ERROR("Screen DC CreateCompatibleDC");
341 /* Create a DIB and select it into the dest_hdc */
342 bmi
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
343 bmi
.bmiHeader
.biWidth
= clip_rect
.right
- clip_rect
.left
;
344 bmi
.bmiHeader
.biHeight
= -(clip_rect
.bottom
- clip_rect
.top
);
345 bmi
.bmiHeader
.biPlanes
= 1;
346 bmi
.bmiHeader
.biBitCount
= bpp
;
347 bmi
.bmiHeader
.biCompression
= BI_RGB
;
348 bmi
.bmiHeader
.biSizeImage
= 0;
349 bmi
.bmiHeader
.biXPelsPerMeter
= 0;
350 bmi
.bmiHeader
.biYPelsPerMeter
= 0;
351 bmi
.bmiHeader
.biClrUsed
= 0;
352 bmi
.bmiHeader
.biClrImportant
= 0;
353 hbmp
= CreateDIBSection(dest_hdc
, &bmi
, DIB_RGB_COLORS
,
356 WIN32_API_ERROR("Creating DIB Section");
361 if (!SelectObject(dest_hdc
, hbmp
)) {
362 WIN32_API_ERROR("SelectObject");
367 /* Get info from the bitmap */
368 GetObject(hbmp
, sizeof(BITMAP
), &bmp
);
370 st
= avformat_new_stream(s1
, NULL
);
372 ret
= AVERROR(ENOMEM
);
375 avpriv_set_pts_info(st
, 64, 1, 1000000); /* 64 bits pts in us */
377 gdigrab
->frame_size
= bmp
.bmWidthBytes
* bmp
.bmHeight
* bmp
.bmPlanes
;
378 gdigrab
->header_size
= sizeof(BITMAPFILEHEADER
) + sizeof(BITMAPINFOHEADER
) +
379 (bpp
<= 8 ? (1 << bpp
) : 0) * sizeof(RGBQUAD
) /* palette size */;
380 gdigrab
->time_base
= av_inv_q(gdigrab
->framerate
);
381 gdigrab
->time_frame
= av_gettime() / av_q2d(gdigrab
->time_base
);
383 gdigrab
->hwnd
= hwnd
;
384 gdigrab
->source_hdc
= source_hdc
;
385 gdigrab
->dest_hdc
= dest_hdc
;
386 gdigrab
->hbmp
= hbmp
;
388 gdigrab
->buffer
= buffer
;
389 gdigrab
->clip_rect
= clip_rect
;
391 gdigrab
->cursor_error_printed
= 0;
393 if (gdigrab
->show_region
) {
394 if (gdigrab_region_wnd_init(s1
, gdigrab
)) {
400 st
->codec
->codec_type
= AVMEDIA_TYPE_VIDEO
;
401 st
->codec
->codec_id
= AV_CODEC_ID_BMP
;
402 st
->codec
->time_base
= gdigrab
->time_base
;
403 st
->codec
->bit_rate
= (gdigrab
->header_size
+ gdigrab
->frame_size
) * 1/av_q2d(gdigrab
->time_base
) * 8;
409 ReleaseDC(hwnd
, source_hdc
);
415 DeleteDC(source_hdc
);
420 * Paints a mouse pointer in a Win32 image.
422 * @param s1 Context of the log information
423 * @param s Current grad structure
425 static void paint_mouse_pointer(AVFormatContext
*s1
, struct gdigrab
*gdigrab
)
429 #define CURSOR_ERROR(str) \
430 if (!gdigrab->cursor_error_printed) { \
431 WIN32_API_ERROR(str); \
432 gdigrab->cursor_error_printed = 1; \
435 ci
.cbSize
= sizeof(ci
);
437 if (GetCursorInfo(&ci
)) {
438 HCURSOR icon
= CopyCursor(ci
.hCursor
);
441 RECT clip_rect
= gdigrab
->clip_rect
;
442 HWND hwnd
= gdigrab
->hwnd
;
444 info
.hbmColor
= NULL
;
446 if (ci
.flags
!= CURSOR_SHOWING
)
450 /* Use the standard arrow cursor as a fallback.
451 * You'll probably only hit this in Wine, which can't fetch
452 * the current system cursor. */
453 icon
= CopyCursor(LoadCursor(NULL
, IDC_ARROW
));
456 if (!GetIconInfo(icon
, &info
)) {
457 CURSOR_ERROR("Could not get icon info");
461 pos
.x
= ci
.ptScreenPos
.x
- clip_rect
.left
- info
.xHotspot
;
462 pos
.y
= ci
.ptScreenPos
.y
- clip_rect
.top
- info
.yHotspot
;
467 if (GetWindowRect(hwnd
, &rect
)) {
471 CURSOR_ERROR("Couldn't get window rectangle");
476 av_log(s1
, AV_LOG_DEBUG
, "Cursor pos (%li,%li) -> (%li,%li)\n",
477 ci
.ptScreenPos
.x
, ci
.ptScreenPos
.y
, pos
.x
, pos
.y
);
479 if (pos
.x
>= 0 && pos
.x
<= clip_rect
.right
- clip_rect
.left
&&
480 pos
.y
>= 0 && pos
.y
<= clip_rect
.bottom
- clip_rect
.top
) {
481 if (!DrawIcon(gdigrab
->dest_hdc
, pos
.x
, pos
.y
, icon
))
482 CURSOR_ERROR("Couldn't draw icon");
487 DeleteObject(info
.hbmMask
);
489 DeleteObject(info
.hbmColor
);
493 CURSOR_ERROR("Couldn't get cursor info");
498 * Grabs a frame from gdi (public device demuxer API).
500 * @param s1 Context from avformat core
501 * @param pkt Packet holding the grabbed frame
502 * @return frame size in bytes
504 static int gdigrab_read_packet(AVFormatContext
*s1
, AVPacket
*pkt
)
506 struct gdigrab
*gdigrab
= s1
->priv_data
;
508 HDC dest_hdc
= gdigrab
->dest_hdc
;
509 HDC source_hdc
= gdigrab
->source_hdc
;
510 RECT clip_rect
= gdigrab
->clip_rect
;
511 AVRational time_base
= gdigrab
->time_base
;
512 int64_t time_frame
= gdigrab
->time_frame
;
514 BITMAPFILEHEADER bfh
;
515 int file_size
= gdigrab
->header_size
+ gdigrab
->frame_size
;
517 int64_t curtime
, delay
;
519 /* Calculate the time of the next frame */
520 time_frame
+= INT64_C(1000000);
522 /* Run Window message processing queue */
523 if (gdigrab
->show_region
)
524 gdigrab_region_wnd_update(s1
, gdigrab
);
526 /* wait based on the frame rate */
528 curtime
= av_gettime();
529 delay
= time_frame
* av_q2d(time_base
) - curtime
;
531 if (delay
< INT64_C(-1000000) * av_q2d(time_base
)) {
532 time_frame
+= INT64_C(1000000);
536 if (s1
->flags
& AVFMT_FLAG_NONBLOCK
) {
537 return AVERROR(EAGAIN
);
543 if (av_new_packet(pkt
, file_size
) < 0)
544 return AVERROR(ENOMEM
);
547 /* Blit screen grab */
548 if (!BitBlt(dest_hdc
, 0, 0,
549 clip_rect
.right
- clip_rect
.left
,
550 clip_rect
.bottom
- clip_rect
.top
,
552 clip_rect
.left
, clip_rect
.top
, SRCCOPY
| CAPTUREBLT
)) {
553 WIN32_API_ERROR("Failed to capture image");
556 if (gdigrab
->draw_mouse
)
557 paint_mouse_pointer(s1
, gdigrab
);
559 /* Copy bits to packet data */
561 bfh
.bfType
= 0x4d42; /* "BM" in little-endian */
562 bfh
.bfSize
= file_size
;
565 bfh
.bfOffBits
= gdigrab
->header_size
;
567 memcpy(pkt
->data
, &bfh
, sizeof(bfh
));
569 memcpy(pkt
->data
+ sizeof(bfh
), &gdigrab
->bmi
.bmiHeader
, sizeof(gdigrab
->bmi
.bmiHeader
));
571 if (gdigrab
->bmi
.bmiHeader
.biBitCount
<= 8)
572 GetDIBColorTable(dest_hdc
, 0, 1 << gdigrab
->bmi
.bmiHeader
.biBitCount
,
573 (RGBQUAD
*) (pkt
->data
+ sizeof(bfh
) + sizeof(gdigrab
->bmi
.bmiHeader
)));
575 memcpy(pkt
->data
+ gdigrab
->header_size
, gdigrab
->buffer
, gdigrab
->frame_size
);
577 gdigrab
->time_frame
= time_frame
;
579 return gdigrab
->header_size
+ gdigrab
->frame_size
;
583 * Closes gdi frame grabber (public device demuxer API).
585 * @param s1 Context from avformat core
586 * @return 0 success, !0 failure
588 static int gdigrab_read_close(AVFormatContext
*s1
)
590 struct gdigrab
*s
= s1
->priv_data
;
593 gdigrab_region_wnd_destroy(s1
, s
);
596 ReleaseDC(s
->hwnd
, s
->source_hdc
);
598 DeleteDC(s
->dest_hdc
);
600 DeleteObject(s
->hbmp
);
602 DeleteDC(s
->source_hdc
);
607 #define OFFSET(x) offsetof(struct gdigrab, x)
608 #define DEC AV_OPT_FLAG_DECODING_PARAM
609 static const AVOption options
[] = {
610 { "draw_mouse", "draw the mouse pointer", OFFSET(draw_mouse
), AV_OPT_TYPE_INT
, {.i64
= 1}, 0, 1, DEC
},
611 { "show_region", "draw border around capture area", OFFSET(show_region
), AV_OPT_TYPE_INT
, {.i64
= 0}, 0, 1, DEC
},
612 { "framerate", "set video frame rate", OFFSET(framerate
), AV_OPT_TYPE_VIDEO_RATE
, {.str
= "ntsc"}, 0, 0, DEC
},
613 { "video_size", "set video frame size", OFFSET(width
), AV_OPT_TYPE_IMAGE_SIZE
, {.str
= NULL
}, 0, 0, DEC
},
614 { "offset_x", "capture area x offset", OFFSET(offset_x
), AV_OPT_TYPE_INT
, {.i64
= 0}, INT_MIN
, INT_MAX
, DEC
},
615 { "offset_y", "capture area y offset", OFFSET(offset_y
), AV_OPT_TYPE_INT
, {.i64
= 0}, INT_MIN
, INT_MAX
, DEC
},
619 static const AVClass gdigrab_class
= {
620 .class_name
= "GDIgrab indev",
621 .item_name
= av_default_item_name
,
623 .version
= LIBAVUTIL_VERSION_INT
,
626 /** gdi grabber device demuxer declaration */
627 AVInputFormat ff_gdigrab_demuxer
= {
629 .long_name
= NULL_IF_CONFIG_SMALL("GDI API Windows frame grabber"),
630 .priv_data_size
= sizeof(struct gdigrab
),
631 .read_header
= gdigrab_read_header
,
632 .read_packet
= gdigrab_read_packet
,
633 .read_close
= gdigrab_read_close
,
634 .flags
= AVFMT_NOFILE
,
635 .priv_class
= &gdigrab_class
,