Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * GDI video grab interface | |
3 | * | |
4 | * This file is part of FFmpeg. | |
5 | * | |
6 | * Copyright (C) 2013 Calvin Walton <calvin.walton@kepstin.ca> | |
7 | * Copyright (C) 2007-2010 Christophe Gisquet <word1.word2@gmail.com> | |
8 | * | |
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. | |
13 | * | |
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. | |
18 | * | |
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 | |
22 | */ | |
23 | ||
24 | /** | |
25 | * @file | |
26 | * GDI frame device demuxer | |
27 | * @author Calvin Walton <calvin.walton@kepstin.ca> | |
28 | * @author Christophe Gisquet <word1.word2@gmail.com> | |
29 | */ | |
30 | ||
31 | #include "config.h" | |
32 | #include "libavformat/internal.h" | |
33 | #include "libavutil/opt.h" | |
34 | #include "libavutil/time.h" | |
35 | #include <windows.h> | |
36 | ||
37 | /** | |
38 | * GDI Device Demuxer context | |
39 | */ | |
40 | struct gdigrab { | |
41 | const AVClass *class; /**< Class for private options */ | |
42 | ||
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 */ | |
47 | ||
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) */ | |
55 | ||
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 */ | |
63 | ||
64 | HWND region_hwnd; /**< Handle of the region border window */ | |
65 | ||
66 | int cursor_error_printed; | |
67 | }; | |
68 | ||
69 | #define WIN32_API_ERROR(str) \ | |
70 | av_log(s1, AV_LOG_ERROR, str " (error %li)\n", GetLastError()) | |
71 | ||
72 | #define REGION_WND_BORDER 3 | |
73 | ||
74 | /** | |
75 | * Callback to handle Windows messages for the region outline window. | |
76 | * | |
77 | * In particular, this handles painting the frame rectangle. | |
78 | * | |
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 | |
84 | */ | |
85 | static LRESULT CALLBACK | |
86 | gdigrab_region_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) | |
87 | { | |
88 | PAINTSTRUCT ps; | |
89 | HDC hdc; | |
90 | RECT rect; | |
91 | ||
92 | switch (msg) { | |
93 | case WM_PAINT: | |
94 | hdc = BeginPaint(hwnd, &ps); | |
95 | ||
96 | GetClientRect(hwnd, &rect); | |
97 | FrameRect(hdc, &rect, GetStockObject(BLACK_BRUSH)); | |
98 | ||
99 | rect.left++; rect.top++; rect.right--; rect.bottom--; | |
100 | FrameRect(hdc, &rect, GetStockObject(WHITE_BRUSH)); | |
101 | ||
102 | rect.left++; rect.top++; rect.right--; rect.bottom--; | |
103 | FrameRect(hdc, &rect, GetStockObject(BLACK_BRUSH)); | |
104 | ||
105 | EndPaint(hwnd, &ps); | |
106 | break; | |
107 | default: | |
108 | return DefWindowProc(hwnd, msg, wparam, lparam); | |
109 | } | |
110 | return 0; | |
111 | } | |
112 | ||
113 | /** | |
114 | * Initialize the region outline window. | |
115 | * | |
116 | * @param s1 The format context. | |
117 | * @param gdigrab gdigrab context. | |
118 | * @return 0 success, !0 failure | |
119 | */ | |
120 | static int | |
121 | gdigrab_region_wnd_init(AVFormatContext *s1, struct gdigrab *gdigrab) | |
122 | { | |
123 | HWND hwnd; | |
124 | RECT rect = gdigrab->clip_rect; | |
125 | HRGN region = NULL; | |
126 | HRGN region_interior = NULL; | |
127 | ||
128 | DWORD style = WS_POPUP | WS_VISIBLE; | |
129 | DWORD ex = WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_TRANSPARENT; | |
130 | ||
131 | rect.left -= REGION_WND_BORDER; rect.top -= REGION_WND_BORDER; | |
132 | rect.right += REGION_WND_BORDER; rect.bottom += REGION_WND_BORDER; | |
133 | ||
134 | AdjustWindowRectEx(&rect, style, FALSE, ex); | |
135 | ||
136 | // Create a window with no owner; use WC_DIALOG instead of writing a custom | |
137 | // window class | |
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); | |
141 | if (!hwnd) { | |
142 | WIN32_API_ERROR("Could not create region display window"); | |
143 | goto error; | |
144 | } | |
145 | ||
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"); | |
156 | goto error; | |
157 | } | |
158 | // The "region" memory is now owned by the window | |
159 | region = NULL; | |
160 | DeleteObject(region_interior); | |
161 | ||
162 | SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR) gdigrab_region_wnd_proc); | |
163 | ||
164 | ShowWindow(hwnd, SW_SHOW); | |
165 | ||
166 | gdigrab->region_hwnd = hwnd; | |
167 | ||
168 | return 0; | |
169 | ||
170 | error: | |
171 | if (region) | |
172 | DeleteObject(region); | |
173 | if (region_interior) | |
174 | DeleteObject(region_interior); | |
175 | if (hwnd) | |
176 | DestroyWindow(hwnd); | |
177 | return 1; | |
178 | } | |
179 | ||
180 | /** | |
181 | * Cleanup/free the region outline window. | |
182 | * | |
183 | * @param s1 The format context. | |
184 | * @param gdigrab gdigrab context. | |
185 | */ | |
186 | static void | |
187 | gdigrab_region_wnd_destroy(AVFormatContext *s1, struct gdigrab *gdigrab) | |
188 | { | |
189 | if (gdigrab->region_hwnd) | |
190 | DestroyWindow(gdigrab->region_hwnd); | |
191 | gdigrab->region_hwnd = NULL; | |
192 | } | |
193 | ||
194 | /** | |
195 | * Process the Windows message queue. | |
196 | * | |
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. | |
200 | * | |
201 | * @param s1 The format context. | |
202 | * @param gdigrab gdigrab context. | |
203 | */ | |
204 | static void | |
205 | gdigrab_region_wnd_update(AVFormatContext *s1, struct gdigrab *gdigrab) | |
206 | { | |
207 | HWND hwnd = gdigrab->region_hwnd; | |
208 | MSG msg; | |
209 | ||
210 | while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)) { | |
211 | DispatchMessage(&msg); | |
212 | } | |
213 | } | |
214 | ||
215 | /** | |
216 | * Initializes the gdi grab device demuxer (public device demuxer API). | |
217 | * | |
218 | * @param s1 Context from avformat core | |
219 | * @return AVERROR_IO error, 0 success | |
220 | */ | |
221 | static int | |
222 | gdigrab_read_header(AVFormatContext *s1) | |
223 | { | |
224 | struct gdigrab *gdigrab = s1->priv_data; | |
225 | ||
226 | HWND hwnd; | |
227 | HDC source_hdc = NULL; | |
228 | HDC dest_hdc = NULL; | |
229 | BITMAPINFO bmi; | |
230 | HBITMAP hbmp = NULL; | |
231 | void *buffer = NULL; | |
232 | ||
233 | const char *filename = s1->filename; | |
234 | const char *name = NULL; | |
235 | AVStream *st = NULL; | |
236 | ||
237 | int bpp; | |
238 | RECT virtual_rect; | |
239 | RECT clip_rect; | |
240 | BITMAP bmp; | |
241 | int ret; | |
242 | ||
243 | if (!strncmp(filename, "title=", 6)) { | |
244 | name = filename + 6; | |
245 | hwnd = FindWindow(NULL, name); | |
246 | if (!hwnd) { | |
247 | av_log(s1, AV_LOG_ERROR, | |
248 | "Can't find window '%s', aborting.\n", name); | |
249 | ret = AVERROR(EIO); | |
250 | goto error; | |
251 | } | |
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; | |
256 | } | |
257 | } else if (!strcmp(filename, "desktop")) { | |
258 | hwnd = NULL; | |
259 | } else { | |
260 | av_log(s1, AV_LOG_ERROR, | |
261 | "Please use \"desktop\" or \"title=<windowname>\" to specify your target.\n"); | |
262 | ret = AVERROR(EIO); | |
263 | goto error; | |
264 | } | |
265 | ||
266 | if (hwnd) { | |
267 | GetClientRect(hwnd, &virtual_rect); | |
268 | } else { | |
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); | |
273 | } | |
274 | ||
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; | |
281 | } else { | |
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; | |
286 | } | |
287 | ||
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); | |
298 | ret = AVERROR(EIO); | |
299 | goto error; | |
300 | } | |
301 | ||
302 | /* This will get the device context for the selected window, or if | |
303 | * none, the primary screen */ | |
304 | source_hdc = GetDC(hwnd); | |
305 | if (!source_hdc) { | |
306 | WIN32_API_ERROR("Couldn't get window device context"); | |
307 | ret = AVERROR(EIO); | |
308 | goto error; | |
309 | } | |
310 | bpp = GetDeviceCaps(source_hdc, BITSPIXEL); | |
311 | ||
312 | if (name) { | |
313 | av_log(s1, AV_LOG_INFO, | |
314 | "Found window %s, capturing %lix%lix%i at (%li,%li)\n", | |
315 | name, | |
316 | clip_rect.right - clip_rect.left, | |
317 | clip_rect.bottom - clip_rect.top, | |
318 | bpp, clip_rect.left, clip_rect.top); | |
319 | } else { | |
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); | |
325 | } | |
326 | ||
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"); | |
330 | ret = AVERROR(EIO); | |
331 | goto error; | |
332 | } | |
333 | ||
334 | dest_hdc = CreateCompatibleDC(source_hdc); | |
335 | if (!dest_hdc) { | |
336 | WIN32_API_ERROR("Screen DC CreateCompatibleDC"); | |
337 | ret = AVERROR(EIO); | |
338 | goto error; | |
339 | } | |
340 | ||
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, | |
354 | &buffer, NULL, 0); | |
355 | if (!hbmp) { | |
356 | WIN32_API_ERROR("Creating DIB Section"); | |
357 | ret = AVERROR(EIO); | |
358 | goto error; | |
359 | } | |
360 | ||
361 | if (!SelectObject(dest_hdc, hbmp)) { | |
362 | WIN32_API_ERROR("SelectObject"); | |
363 | ret = AVERROR(EIO); | |
364 | goto error; | |
365 | } | |
366 | ||
367 | /* Get info from the bitmap */ | |
368 | GetObject(hbmp, sizeof(BITMAP), &bmp); | |
369 | ||
370 | st = avformat_new_stream(s1, NULL); | |
371 | if (!st) { | |
372 | ret = AVERROR(ENOMEM); | |
373 | goto error; | |
374 | } | |
375 | avpriv_set_pts_info(st, 64, 1, 1000000); /* 64 bits pts in us */ | |
376 | ||
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); | |
382 | ||
383 | gdigrab->hwnd = hwnd; | |
384 | gdigrab->source_hdc = source_hdc; | |
385 | gdigrab->dest_hdc = dest_hdc; | |
386 | gdigrab->hbmp = hbmp; | |
387 | gdigrab->bmi = bmi; | |
388 | gdigrab->buffer = buffer; | |
389 | gdigrab->clip_rect = clip_rect; | |
390 | ||
391 | gdigrab->cursor_error_printed = 0; | |
392 | ||
393 | if (gdigrab->show_region) { | |
394 | if (gdigrab_region_wnd_init(s1, gdigrab)) { | |
395 | ret = AVERROR(EIO); | |
396 | goto error; | |
397 | } | |
398 | } | |
399 | ||
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; | |
404 | ||
405 | return 0; | |
406 | ||
407 | error: | |
408 | if (source_hdc) | |
409 | ReleaseDC(hwnd, source_hdc); | |
410 | if (dest_hdc) | |
411 | DeleteDC(dest_hdc); | |
412 | if (hbmp) | |
413 | DeleteObject(hbmp); | |
414 | if (source_hdc) | |
415 | DeleteDC(source_hdc); | |
416 | return ret; | |
417 | } | |
418 | ||
419 | /** | |
420 | * Paints a mouse pointer in a Win32 image. | |
421 | * | |
422 | * @param s1 Context of the log information | |
423 | * @param s Current grad structure | |
424 | */ | |
425 | static void paint_mouse_pointer(AVFormatContext *s1, struct gdigrab *gdigrab) | |
426 | { | |
427 | CURSORINFO ci = {0}; | |
428 | ||
429 | #define CURSOR_ERROR(str) \ | |
430 | if (!gdigrab->cursor_error_printed) { \ | |
431 | WIN32_API_ERROR(str); \ | |
432 | gdigrab->cursor_error_printed = 1; \ | |
433 | } | |
434 | ||
435 | ci.cbSize = sizeof(ci); | |
436 | ||
437 | if (GetCursorInfo(&ci)) { | |
438 | HCURSOR icon = CopyCursor(ci.hCursor); | |
439 | ICONINFO info; | |
440 | POINT pos; | |
441 | RECT clip_rect = gdigrab->clip_rect; | |
442 | HWND hwnd = gdigrab->hwnd; | |
443 | info.hbmMask = NULL; | |
444 | info.hbmColor = NULL; | |
445 | ||
446 | if (ci.flags != CURSOR_SHOWING) | |
447 | return; | |
448 | ||
449 | if (!icon) { | |
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)); | |
454 | } | |
455 | ||
456 | if (!GetIconInfo(icon, &info)) { | |
457 | CURSOR_ERROR("Could not get icon info"); | |
458 | goto icon_error; | |
459 | } | |
460 | ||
461 | pos.x = ci.ptScreenPos.x - clip_rect.left - info.xHotspot; | |
462 | pos.y = ci.ptScreenPos.y - clip_rect.top - info.yHotspot; | |
463 | ||
464 | if (hwnd) { | |
465 | RECT rect; | |
466 | ||
467 | if (GetWindowRect(hwnd, &rect)) { | |
468 | pos.x -= rect.left; | |
469 | pos.y -= rect.top; | |
470 | } else { | |
471 | CURSOR_ERROR("Couldn't get window rectangle"); | |
472 | goto icon_error; | |
473 | } | |
474 | } | |
475 | ||
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); | |
478 | ||
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"); | |
483 | } | |
484 | ||
485 | icon_error: | |
486 | if (info.hbmMask) | |
487 | DeleteObject(info.hbmMask); | |
488 | if (info.hbmColor) | |
489 | DeleteObject(info.hbmColor); | |
490 | if (icon) | |
491 | DestroyCursor(icon); | |
492 | } else { | |
493 | CURSOR_ERROR("Couldn't get cursor info"); | |
494 | } | |
495 | } | |
496 | ||
497 | /** | |
498 | * Grabs a frame from gdi (public device demuxer API). | |
499 | * | |
500 | * @param s1 Context from avformat core | |
501 | * @param pkt Packet holding the grabbed frame | |
502 | * @return frame size in bytes | |
503 | */ | |
504 | static int gdigrab_read_packet(AVFormatContext *s1, AVPacket *pkt) | |
505 | { | |
506 | struct gdigrab *gdigrab = s1->priv_data; | |
507 | ||
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; | |
513 | ||
514 | BITMAPFILEHEADER bfh; | |
515 | int file_size = gdigrab->header_size + gdigrab->frame_size; | |
516 | ||
517 | int64_t curtime, delay; | |
518 | ||
519 | /* Calculate the time of the next frame */ | |
520 | time_frame += INT64_C(1000000); | |
521 | ||
522 | /* Run Window message processing queue */ | |
523 | if (gdigrab->show_region) | |
524 | gdigrab_region_wnd_update(s1, gdigrab); | |
525 | ||
526 | /* wait based on the frame rate */ | |
527 | for (;;) { | |
528 | curtime = av_gettime(); | |
529 | delay = time_frame * av_q2d(time_base) - curtime; | |
530 | if (delay <= 0) { | |
531 | if (delay < INT64_C(-1000000) * av_q2d(time_base)) { | |
532 | time_frame += INT64_C(1000000); | |
533 | } | |
534 | break; | |
535 | } | |
536 | if (s1->flags & AVFMT_FLAG_NONBLOCK) { | |
537 | return AVERROR(EAGAIN); | |
538 | } else { | |
539 | av_usleep(delay); | |
540 | } | |
541 | } | |
542 | ||
543 | if (av_new_packet(pkt, file_size) < 0) | |
544 | return AVERROR(ENOMEM); | |
545 | pkt->pts = curtime; | |
546 | ||
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, | |
551 | source_hdc, | |
552 | clip_rect.left, clip_rect.top, SRCCOPY | CAPTUREBLT)) { | |
553 | WIN32_API_ERROR("Failed to capture image"); | |
554 | return AVERROR(EIO); | |
555 | } | |
556 | if (gdigrab->draw_mouse) | |
557 | paint_mouse_pointer(s1, gdigrab); | |
558 | ||
559 | /* Copy bits to packet data */ | |
560 | ||
561 | bfh.bfType = 0x4d42; /* "BM" in little-endian */ | |
562 | bfh.bfSize = file_size; | |
563 | bfh.bfReserved1 = 0; | |
564 | bfh.bfReserved2 = 0; | |
565 | bfh.bfOffBits = gdigrab->header_size; | |
566 | ||
567 | memcpy(pkt->data, &bfh, sizeof(bfh)); | |
568 | ||
569 | memcpy(pkt->data + sizeof(bfh), &gdigrab->bmi.bmiHeader, sizeof(gdigrab->bmi.bmiHeader)); | |
570 | ||
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))); | |
574 | ||
575 | memcpy(pkt->data + gdigrab->header_size, gdigrab->buffer, gdigrab->frame_size); | |
576 | ||
577 | gdigrab->time_frame = time_frame; | |
578 | ||
579 | return gdigrab->header_size + gdigrab->frame_size; | |
580 | } | |
581 | ||
582 | /** | |
583 | * Closes gdi frame grabber (public device demuxer API). | |
584 | * | |
585 | * @param s1 Context from avformat core | |
586 | * @return 0 success, !0 failure | |
587 | */ | |
588 | static int gdigrab_read_close(AVFormatContext *s1) | |
589 | { | |
590 | struct gdigrab *s = s1->priv_data; | |
591 | ||
592 | if (s->show_region) | |
593 | gdigrab_region_wnd_destroy(s1, s); | |
594 | ||
595 | if (s->source_hdc) | |
596 | ReleaseDC(s->hwnd, s->source_hdc); | |
597 | if (s->dest_hdc) | |
598 | DeleteDC(s->dest_hdc); | |
599 | if (s->hbmp) | |
600 | DeleteObject(s->hbmp); | |
601 | if (s->source_hdc) | |
602 | DeleteDC(s->source_hdc); | |
603 | ||
604 | return 0; | |
605 | } | |
606 | ||
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 }, | |
616 | { NULL }, | |
617 | }; | |
618 | ||
619 | static const AVClass gdigrab_class = { | |
620 | .class_name = "GDIgrab indev", | |
621 | .item_name = av_default_item_name, | |
622 | .option = options, | |
623 | .version = LIBAVUTIL_VERSION_INT, | |
624 | }; | |
625 | ||
626 | /** gdi grabber device demuxer declaration */ | |
627 | AVInputFormat ff_gdigrab_demuxer = { | |
628 | .name = "gdigrab", | |
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, | |
636 | }; |