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/demux.h"
33 #include "libavformat/internal.h"
34 #include "libavutil/mem.h"
35 #include "libavutil/opt.h"
36 #include "libavutil/time.h"
37 #include "libavutil/wchar_filename.h"
41 * GDI Device Demuxer context
44 const AVClass
*class; /**< Class for private options */
46 int frame_size
; /**< Size in bytes of the frame pixel data */
47 int header_size
; /**< Size in bytes of the DIB header */
48 AVRational time_base
; /**< Time base */
49 int64_t time_frame
; /**< Current time */
51 int draw_mouse
; /**< Draw mouse cursor (private option) */
52 int show_region
; /**< Draw border (private option) */
53 AVRational framerate
; /**< Capture framerate (private option) */
54 int width
; /**< Width of the grab frame (private option) */
55 int height
; /**< Height of the grab frame (private option) */
56 int offset_x
; /**< Capture x offset (private option) */
57 int offset_y
; /**< Capture y offset (private option) */
59 HWND hwnd
; /**< Handle of the window for the grab */
60 HDC source_hdc
; /**< Source device context */
61 HDC dest_hdc
; /**< Destination, source-compatible DC */
62 BITMAPINFO bmi
; /**< Information describing DIB format */
63 HBITMAP hbmp
; /**< Information on the bitmap captured */
64 void *buffer
; /**< The buffer containing the bitmap image data */
65 RECT clip_rect
; /**< The subarea of the screen or window to clip */
67 HWND region_hwnd
; /**< Handle of the region border window */
69 int cursor_error_printed
;
72 #define WIN32_API_ERROR(str) \
73 av_log(s1, AV_LOG_ERROR, str " (error %li)\n", GetLastError())
75 #define REGION_WND_BORDER 3
78 * Callback to handle Windows messages for the region outline window.
80 * In particular, this handles painting the frame rectangle.
82 * @param hwnd The region outline window handle.
83 * @param msg The Windows message.
84 * @param wparam First Windows message parameter.
85 * @param lparam Second Windows message parameter.
86 * @return 0 success, !0 failure
88 static LRESULT CALLBACK
89 gdigrab_region_wnd_proc(HWND hwnd
, UINT msg
, WPARAM wparam
, LPARAM lparam
)
97 hdc
= BeginPaint(hwnd
, &ps
);
99 GetClientRect(hwnd
, &rect
);
100 FrameRect(hdc
, &rect
, GetStockObject(BLACK_BRUSH
));
102 rect
.left
++; rect
.top
++; rect
.right
--; rect
.bottom
--;
103 FrameRect(hdc
, &rect
, GetStockObject(WHITE_BRUSH
));
105 rect
.left
++; rect
.top
++; rect
.right
--; rect
.bottom
--;
106 FrameRect(hdc
, &rect
, GetStockObject(BLACK_BRUSH
));
111 return DefWindowProc(hwnd
, msg
, wparam
, lparam
);
117 * Initialize the region outline window.
119 * @param s1 The format context.
120 * @param gdigrab gdigrab context.
121 * @return 0 success, !0 failure
124 gdigrab_region_wnd_init(AVFormatContext
*s1
, struct gdigrab
*gdigrab
)
127 RECT rect
= gdigrab
->clip_rect
;
129 HRGN region_interior
= NULL
;
131 DWORD style
= WS_POPUP
| WS_VISIBLE
;
132 DWORD ex
= WS_EX_TOOLWINDOW
| WS_EX_TOPMOST
| WS_EX_TRANSPARENT
;
134 rect
.left
-= REGION_WND_BORDER
; rect
.top
-= REGION_WND_BORDER
;
135 rect
.right
+= REGION_WND_BORDER
; rect
.bottom
+= REGION_WND_BORDER
;
137 AdjustWindowRectEx(&rect
, style
, FALSE
, ex
);
139 // Create a window with no owner; use WC_DIALOG instead of writing a custom
141 hwnd
= CreateWindowEx(ex
, WC_DIALOG
, NULL
, style
, rect
.left
, rect
.top
,
142 rect
.right
- rect
.left
, rect
.bottom
- rect
.top
,
143 NULL
, NULL
, NULL
, NULL
);
145 WIN32_API_ERROR("Could not create region display window");
149 // Set the window shape to only include the border area
150 GetClientRect(hwnd
, &rect
);
151 region
= CreateRectRgn(0, 0,
152 rect
.right
- rect
.left
, rect
.bottom
- rect
.top
);
153 region_interior
= CreateRectRgn(REGION_WND_BORDER
, REGION_WND_BORDER
,
154 rect
.right
- rect
.left
- REGION_WND_BORDER
,
155 rect
.bottom
- rect
.top
- REGION_WND_BORDER
);
156 CombineRgn(region
, region
, region_interior
, RGN_DIFF
);
157 if (!SetWindowRgn(hwnd
, region
, FALSE
)) {
158 WIN32_API_ERROR("Could not set window region");
161 // The "region" memory is now owned by the window
163 DeleteObject(region_interior
);
165 SetWindowLongPtr(hwnd
, GWLP_WNDPROC
, (LONG_PTR
) gdigrab_region_wnd_proc
);
167 ShowWindow(hwnd
, SW_SHOW
);
169 gdigrab
->region_hwnd
= hwnd
;
175 DeleteObject(region
);
177 DeleteObject(region_interior
);
184 * Cleanup/free the region outline window.
186 * @param s1 The format context.
187 * @param gdigrab gdigrab context.
190 gdigrab_region_wnd_destroy(AVFormatContext
*s1
, struct gdigrab
*gdigrab
)
192 if (gdigrab
->region_hwnd
)
193 DestroyWindow(gdigrab
->region_hwnd
);
194 gdigrab
->region_hwnd
= NULL
;
198 * Process the Windows message queue.
200 * This is important to prevent Windows from thinking the window has become
201 * unresponsive. As well, things like WM_PAINT (to actually draw the window
202 * contents) are handled from the message queue context.
204 * @param s1 The format context.
205 * @param gdigrab gdigrab context.
208 gdigrab_region_wnd_update(AVFormatContext
*s1
, struct gdigrab
*gdigrab
)
210 HWND hwnd
= gdigrab
->region_hwnd
;
213 while (PeekMessage(&msg
, hwnd
, 0, 0, PM_REMOVE
)) {
214 DispatchMessage(&msg
);
219 * Initializes the gdi grab device demuxer (public device demuxer API).
221 * @param s1 Context from avformat core
222 * @return AVERROR_IO error, 0 success
225 gdigrab_read_header(AVFormatContext
*s1
)
227 struct gdigrab
*gdigrab
= s1
->priv_data
;
230 HDC source_hdc
= NULL
;
236 const char *filename
= s1
->url
;
237 const char *name
= NULL
;
250 if (!strncmp(filename
, "title=", 6)) {
251 wchar_t *name_w
= NULL
;
254 if(utf8towchar(name
, &name_w
)) {
255 ret
= AVERROR(errno
);
259 ret
= AVERROR(EINVAL
);
263 hwnd
= FindWindowW(NULL
, name_w
);
266 av_log(s1
, AV_LOG_ERROR
,
267 "Can't find window '%s', aborting.\n", name
);
271 if (gdigrab
->show_region
) {
272 av_log(s1
, AV_LOG_WARNING
,
273 "Can't show region when grabbing a window.\n");
274 gdigrab
->show_region
= 0;
276 } else if (!strcmp(filename
, "desktop")) {
278 } else if (!strncmp(filename
, "hwnd=", 5)) {
282 hwnd
= (HWND
) strtoull(name
, &p
, 0);
284 if (p
== NULL
|| p
== name
|| p
[0] != '\0')
286 av_log(s1
, AV_LOG_ERROR
,
287 "Invalid window handle '%s', must be a valid integer.\n", name
);
288 ret
= AVERROR(EINVAL
);
292 av_log(s1
, AV_LOG_ERROR
,
293 "Please use \"desktop\", \"title=<windowname>\" or \"hwnd=<hwnd>\" to specify your target.\n");
298 /* This will get the device context for the selected window, or if
299 * none, the primary screen */
300 source_hdc
= GetDC(hwnd
);
302 WIN32_API_ERROR("Couldn't get window device context");
306 bpp
= GetDeviceCaps(source_hdc
, BITSPIXEL
);
308 horzres
= GetDeviceCaps(source_hdc
, HORZRES
);
309 vertres
= GetDeviceCaps(source_hdc
, VERTRES
);
310 desktophorzres
= GetDeviceCaps(source_hdc
, DESKTOPHORZRES
);
311 desktopvertres
= GetDeviceCaps(source_hdc
, DESKTOPVERTRES
);
314 GetClientRect(hwnd
, &virtual_rect
);
315 /* window -- get the right height and width for scaling DPI */
316 virtual_rect
.left
= virtual_rect
.left
* desktophorzres
/ horzres
;
317 virtual_rect
.right
= virtual_rect
.right
* desktophorzres
/ horzres
;
318 virtual_rect
.top
= virtual_rect
.top
* desktopvertres
/ vertres
;
319 virtual_rect
.bottom
= virtual_rect
.bottom
* desktopvertres
/ vertres
;
321 /* desktop -- get the right height and width for scaling DPI */
322 virtual_rect
.left
= GetSystemMetrics(SM_XVIRTUALSCREEN
);
323 virtual_rect
.top
= GetSystemMetrics(SM_YVIRTUALSCREEN
);
324 virtual_rect
.right
= (virtual_rect
.left
+ GetSystemMetrics(SM_CXVIRTUALSCREEN
)) * desktophorzres
/ horzres
;
325 virtual_rect
.bottom
= (virtual_rect
.top
+ GetSystemMetrics(SM_CYVIRTUALSCREEN
)) * desktopvertres
/ vertres
;
328 /* If no width or height set, use full screen/window area */
329 if (!gdigrab
->width
|| !gdigrab
->height
) {
330 clip_rect
.left
= virtual_rect
.left
;
331 clip_rect
.top
= virtual_rect
.top
;
332 clip_rect
.right
= virtual_rect
.right
;
333 clip_rect
.bottom
= virtual_rect
.bottom
;
335 clip_rect
.left
= gdigrab
->offset_x
;
336 clip_rect
.top
= gdigrab
->offset_y
;
337 clip_rect
.right
= gdigrab
->width
+ gdigrab
->offset_x
;
338 clip_rect
.bottom
= gdigrab
->height
+ gdigrab
->offset_y
;
341 if (clip_rect
.left
< virtual_rect
.left
||
342 clip_rect
.top
< virtual_rect
.top
||
343 clip_rect
.right
> virtual_rect
.right
||
344 clip_rect
.bottom
> virtual_rect
.bottom
) {
345 av_log(s1
, AV_LOG_ERROR
,
346 "Capture area (%li,%li),(%li,%li) extends outside window area (%li,%li),(%li,%li)",
347 clip_rect
.left
, clip_rect
.top
,
348 clip_rect
.right
, clip_rect
.bottom
,
349 virtual_rect
.left
, virtual_rect
.top
,
350 virtual_rect
.right
, virtual_rect
.bottom
);
357 av_log(s1
, AV_LOG_INFO
,
358 "Found window %s, capturing %lix%lix%i at (%li,%li)\n",
360 clip_rect
.right
- clip_rect
.left
,
361 clip_rect
.bottom
- clip_rect
.top
,
362 bpp
, clip_rect
.left
, clip_rect
.top
);
364 av_log(s1
, AV_LOG_INFO
,
365 "Capturing whole desktop as %lix%lix%i at (%li,%li)\n",
366 clip_rect
.right
- clip_rect
.left
,
367 clip_rect
.bottom
- clip_rect
.top
,
368 bpp
, clip_rect
.left
, clip_rect
.top
);
371 if (clip_rect
.right
- clip_rect
.left
<= 0 ||
372 clip_rect
.bottom
- clip_rect
.top
<= 0 || bpp
%8) {
373 av_log(s1
, AV_LOG_ERROR
, "Invalid properties, aborting\n");
378 dest_hdc
= CreateCompatibleDC(source_hdc
);
380 WIN32_API_ERROR("Screen DC CreateCompatibleDC");
385 /* Create a DIB and select it into the dest_hdc */
386 bmi
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
387 bmi
.bmiHeader
.biWidth
= clip_rect
.right
- clip_rect
.left
;
388 bmi
.bmiHeader
.biHeight
= -(clip_rect
.bottom
- clip_rect
.top
);
389 bmi
.bmiHeader
.biPlanes
= 1;
390 bmi
.bmiHeader
.biBitCount
= bpp
;
391 bmi
.bmiHeader
.biCompression
= BI_RGB
;
392 bmi
.bmiHeader
.biSizeImage
= 0;
393 bmi
.bmiHeader
.biXPelsPerMeter
= 0;
394 bmi
.bmiHeader
.biYPelsPerMeter
= 0;
395 bmi
.bmiHeader
.biClrUsed
= 0;
396 bmi
.bmiHeader
.biClrImportant
= 0;
397 hbmp
= CreateDIBSection(dest_hdc
, &bmi
, DIB_RGB_COLORS
,
400 WIN32_API_ERROR("Creating DIB Section");
405 if (!SelectObject(dest_hdc
, hbmp
)) {
406 WIN32_API_ERROR("SelectObject");
411 /* Get info from the bitmap */
412 GetObject(hbmp
, sizeof(BITMAP
), &bmp
);
414 st
= avformat_new_stream(s1
, NULL
);
416 ret
= AVERROR(ENOMEM
);
419 avpriv_set_pts_info(st
, 64, 1, 1000000); /* 64 bits pts in us */
421 gdigrab
->frame_size
= bmp
.bmWidthBytes
* bmp
.bmHeight
* bmp
.bmPlanes
;
422 gdigrab
->header_size
= sizeof(BITMAPFILEHEADER
) + sizeof(BITMAPINFOHEADER
) +
423 (bpp
<= 8 ? (1 << bpp
) : 0) * sizeof(RGBQUAD
) /* palette size */;
424 gdigrab
->time_base
= av_inv_q(gdigrab
->framerate
);
425 gdigrab
->time_frame
= av_gettime_relative() / av_q2d(gdigrab
->time_base
);
427 gdigrab
->hwnd
= hwnd
;
428 gdigrab
->source_hdc
= source_hdc
;
429 gdigrab
->dest_hdc
= dest_hdc
;
430 gdigrab
->hbmp
= hbmp
;
432 gdigrab
->buffer
= buffer
;
433 gdigrab
->clip_rect
= clip_rect
;
435 gdigrab
->cursor_error_printed
= 0;
437 if (gdigrab
->show_region
) {
438 if (gdigrab_region_wnd_init(s1
, gdigrab
)) {
444 st
->avg_frame_rate
= av_inv_q(gdigrab
->time_base
);
446 st
->codecpar
->codec_type
= AVMEDIA_TYPE_VIDEO
;
447 st
->codecpar
->codec_id
= AV_CODEC_ID_BMP
;
448 st
->codecpar
->bit_rate
= (gdigrab
->header_size
+ gdigrab
->frame_size
) * 1/av_q2d(gdigrab
->time_base
) * 8;
454 ReleaseDC(hwnd
, source_hdc
);
460 DeleteDC(source_hdc
);
465 * Paints a mouse pointer in a Win32 image.
467 * @param s1 Context of the log information
468 * @param s Current grad structure
470 static void paint_mouse_pointer(AVFormatContext
*s1
, struct gdigrab
*gdigrab
)
474 #define CURSOR_ERROR(str) \
475 if (!gdigrab->cursor_error_printed) { \
476 WIN32_API_ERROR(str); \
477 gdigrab->cursor_error_printed = 1; \
480 ci
.cbSize
= sizeof(ci
);
482 if (GetCursorInfo(&ci
)) {
483 HCURSOR icon
= CopyCursor(ci
.hCursor
);
486 RECT clip_rect
= gdigrab
->clip_rect
;
487 HWND hwnd
= gdigrab
->hwnd
;
488 int horzres
= GetDeviceCaps(gdigrab
->source_hdc
, HORZRES
);
489 int vertres
= GetDeviceCaps(gdigrab
->source_hdc
, VERTRES
);
490 int desktophorzres
= GetDeviceCaps(gdigrab
->source_hdc
, DESKTOPHORZRES
);
491 int desktopvertres
= GetDeviceCaps(gdigrab
->source_hdc
, DESKTOPVERTRES
);
493 info
.hbmColor
= NULL
;
495 if (ci
.flags
!= CURSOR_SHOWING
)
499 /* Use the standard arrow cursor as a fallback.
500 * You'll probably only hit this in Wine, which can't fetch
501 * the current system cursor. */
502 icon
= CopyCursor(LoadCursor(NULL
, IDC_ARROW
));
505 if (!GetIconInfo(icon
, &info
)) {
506 CURSOR_ERROR("Could not get icon info");
513 if (GetWindowRect(hwnd
, &rect
)) {
514 pos
.x
= ci
.ptScreenPos
.x
- clip_rect
.left
- info
.xHotspot
- rect
.left
;
515 pos
.y
= ci
.ptScreenPos
.y
- clip_rect
.top
- info
.yHotspot
- rect
.top
;
517 //that would keep the correct location of mouse with hidpi screens
518 pos
.x
= pos
.x
* desktophorzres
/ horzres
;
519 pos
.y
= pos
.y
* desktopvertres
/ vertres
;
521 CURSOR_ERROR("Couldn't get window rectangle");
525 //that would keep the correct location of mouse with hidpi screens
526 pos
.x
= ci
.ptScreenPos
.x
* desktophorzres
/ horzres
- clip_rect
.left
- info
.xHotspot
;
527 pos
.y
= ci
.ptScreenPos
.y
* desktopvertres
/ vertres
- clip_rect
.top
- info
.yHotspot
;
530 av_log(s1
, AV_LOG_DEBUG
, "Cursor pos (%li,%li) -> (%li,%li)\n",
531 ci
.ptScreenPos
.x
, ci
.ptScreenPos
.y
, pos
.x
, pos
.y
);
533 if (pos
.x
>= 0 && pos
.x
<= clip_rect
.right
- clip_rect
.left
&&
534 pos
.y
>= 0 && pos
.y
<= clip_rect
.bottom
- clip_rect
.top
) {
535 if (!DrawIcon(gdigrab
->dest_hdc
, pos
.x
, pos
.y
, icon
))
536 CURSOR_ERROR("Couldn't draw icon");
541 DeleteObject(info
.hbmMask
);
543 DeleteObject(info
.hbmColor
);
547 CURSOR_ERROR("Couldn't get cursor info");
552 * Grabs a frame from gdi (public device demuxer API).
554 * @param s1 Context from avformat core
555 * @param pkt Packet holding the grabbed frame
556 * @return frame size in bytes
558 static int gdigrab_read_packet(AVFormatContext
*s1
, AVPacket
*pkt
)
560 struct gdigrab
*gdigrab
= s1
->priv_data
;
562 HDC dest_hdc
= gdigrab
->dest_hdc
;
563 HDC source_hdc
= gdigrab
->source_hdc
;
564 RECT clip_rect
= gdigrab
->clip_rect
;
565 AVRational time_base
= gdigrab
->time_base
;
566 int64_t time_frame
= gdigrab
->time_frame
;
568 BITMAPFILEHEADER bfh
;
569 int file_size
= gdigrab
->header_size
+ gdigrab
->frame_size
;
571 int64_t curtime
, delay
;
573 /* Calculate the time of the next frame */
574 time_frame
+= INT64_C(1000000);
576 /* Run Window message processing queue */
577 if (gdigrab
->show_region
)
578 gdigrab_region_wnd_update(s1
, gdigrab
);
580 /* wait based on the frame rate */
582 curtime
= av_gettime_relative();
583 delay
= time_frame
* av_q2d(time_base
) - curtime
;
585 if (delay
< INT64_C(-1000000) * av_q2d(time_base
)) {
586 time_frame
+= INT64_C(1000000);
590 if (s1
->flags
& AVFMT_FLAG_NONBLOCK
) {
591 return AVERROR(EAGAIN
);
597 if (av_new_packet(pkt
, file_size
) < 0)
598 return AVERROR(ENOMEM
);
599 pkt
->pts
= av_gettime();
601 /* Blit screen grab */
602 if (!BitBlt(dest_hdc
, 0, 0,
603 clip_rect
.right
- clip_rect
.left
,
604 clip_rect
.bottom
- clip_rect
.top
,
606 clip_rect
.left
, clip_rect
.top
, SRCCOPY
| CAPTUREBLT
)) {
607 WIN32_API_ERROR("Failed to capture image");
610 if (gdigrab
->draw_mouse
)
611 paint_mouse_pointer(s1
, gdigrab
);
613 /* Copy bits to packet data */
615 bfh
.bfType
= 0x4d42; /* "BM" in little-endian */
616 bfh
.bfSize
= file_size
;
619 bfh
.bfOffBits
= gdigrab
->header_size
;
621 memcpy(pkt
->data
, &bfh
, sizeof(bfh
));
623 memcpy(pkt
->data
+ sizeof(bfh
), &gdigrab
->bmi
.bmiHeader
, sizeof(gdigrab
->bmi
.bmiHeader
));
625 if (gdigrab
->bmi
.bmiHeader
.biBitCount
<= 8)
626 GetDIBColorTable(dest_hdc
, 0, 1 << gdigrab
->bmi
.bmiHeader
.biBitCount
,
627 (RGBQUAD
*) (pkt
->data
+ sizeof(bfh
) + sizeof(gdigrab
->bmi
.bmiHeader
)));
629 memcpy(pkt
->data
+ gdigrab
->header_size
, gdigrab
->buffer
, gdigrab
->frame_size
);
631 gdigrab
->time_frame
= time_frame
;
633 return gdigrab
->header_size
+ gdigrab
->frame_size
;
637 * Closes gdi frame grabber (public device demuxer API).
639 * @param s1 Context from avformat core
640 * @return 0 success, !0 failure
642 static int gdigrab_read_close(AVFormatContext
*s1
)
644 struct gdigrab
*s
= s1
->priv_data
;
647 gdigrab_region_wnd_destroy(s1
, s
);
650 ReleaseDC(s
->hwnd
, s
->source_hdc
);
652 DeleteDC(s
->dest_hdc
);
654 DeleteObject(s
->hbmp
);
656 DeleteDC(s
->source_hdc
);
661 #define OFFSET(x) offsetof(struct gdigrab, x)
662 #define DEC AV_OPT_FLAG_DECODING_PARAM
663 static const AVOption options
[] = {
664 { "draw_mouse", "draw the mouse pointer", OFFSET(draw_mouse
), AV_OPT_TYPE_INT
, {.i64
= 1}, 0, 1, DEC
},
665 { "show_region", "draw border around capture area", OFFSET(show_region
), AV_OPT_TYPE_INT
, {.i64
= 0}, 0, 1, DEC
},
666 { "framerate", "set video frame rate", OFFSET(framerate
), AV_OPT_TYPE_VIDEO_RATE
, {.str
= "ntsc"}, 0, INT_MAX
, DEC
},
667 { "video_size", "set video frame size", OFFSET(width
), AV_OPT_TYPE_IMAGE_SIZE
, {.str
= NULL
}, 0, 0, DEC
},
668 { "offset_x", "capture area x offset", OFFSET(offset_x
), AV_OPT_TYPE_INT
, {.i64
= 0}, INT_MIN
, INT_MAX
, DEC
},
669 { "offset_y", "capture area y offset", OFFSET(offset_y
), AV_OPT_TYPE_INT
, {.i64
= 0}, INT_MIN
, INT_MAX
, DEC
},
673 static const AVClass gdigrab_class
= {
674 .class_name
= "GDIgrab indev",
675 .item_name
= av_default_item_name
,
677 .version
= LIBAVUTIL_VERSION_INT
,
678 .category
= AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT
,
681 /** gdi grabber device demuxer declaration */
682 const FFInputFormat ff_gdigrab_demuxer
= {
684 .p
.long_name
= NULL_IF_CONFIG_SMALL("GDI API Windows frame grabber"),
685 .p
.flags
= AVFMT_NOFILE
,
686 .p
.priv_class
= &gdigrab_class
,
687 .priv_data_size
= sizeof(struct gdigrab
),
688 .read_header
= gdigrab_read_header
,
689 .read_packet
= gdigrab_read_packet
,
690 .read_close
= gdigrab_read_close
,