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>
pull/64/head
Calvin Walton 11 years ago committed by Michael Niedermayer
parent f359bac27c
commit 08909fb56b
  1. 1
      Changelog
  2. 5
      configure
  3. 1
      doc/general.texi
  4. 75
      doc/indevs.texi
  5. 1
      libavdevice/Makefile
  6. 1
      libavdevice/alldevices.c
  7. 630
      libavdevice/gdigrab.c

@ -14,6 +14,7 @@ version <next>:
- QTKit input device - QTKit input device
- improvments to OpenEXR image decoder - improvments to OpenEXR image decoder
- support decoding 16-bit RLE SGI images - support decoding 16-bit RLE SGI images
- GDI screen grabbing for Windows
version 2.2: version 2.2:

5
configure vendored

@ -2349,6 +2349,9 @@ dv1394_indev_deps="dv1394"
dv1394_indev_select="dv_demuxer" dv1394_indev_select="dv_demuxer"
fbdev_indev_deps="linux_fb_h" fbdev_indev_deps="linux_fb_h"
fbdev_outdev_deps="linux_fb_h" fbdev_outdev_deps="linux_fb_h"
gdigrab_indev_deps="CreateDIBSection"
gdigrab_indev_extralibs="-lgdi32"
gdigrab_indev_select="bmp_decoder"
iec61883_indev_deps="libiec61883" iec61883_indev_deps="libiec61883"
jack_indev_deps="jack_jack_h sem_timedwait" jack_indev_deps="jack_jack_h sem_timedwait"
lavfi_indev_deps="avfilter" lavfi_indev_deps="avfilter"
@ -4741,6 +4744,8 @@ require Xext X11/extensions/XShm.h XShmCreateImage -lXext &&
require Xfixes X11/extensions/Xfixes.h XFixesGetCursorImage -lXfixes && require Xfixes X11/extensions/Xfixes.h XFixesGetCursorImage -lXfixes &&
{ enabled xlib || die "ERROR: Xlib not found"; } { enabled xlib || die "ERROR: Xlib not found"; }
check_func_headers "windows.h" CreateDIBSection "$gdigrab_indev_extralibs"
enabled vaapi && enabled vaapi &&
check_lib va/va.h vaInitialize -lva || check_lib va/va.h vaInitialize -lva ||
disable vaapi disable vaapi

@ -1088,6 +1088,7 @@ performance on systems without hardware floating point support).
@item Video4Linux2 @tab X @tab X @item Video4Linux2 @tab X @tab X
@item VfW capture @tab X @tab @item VfW capture @tab X @tab
@item X11 grabbing @tab X @tab @item X11 grabbing @tab X @tab
@item Win32 grabbing @tab X @tab
@end multitable @end multitable
@code{X} means that input/output is supported. @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
See also @url{http://linux-fbdev.sourceforge.net/}, and fbset(1). See also @url{http://linux-fbdev.sourceforge.net/}, and fbset(1).
@section gdigrab
Win32 GDI-based screen capture device.
This device allows you to capture a region of the display on Windows.
There are two options for the input filename:
@example
desktop
@end example
or
@example
title=@var{window_title}
@end example
The first option will capture the entire desktop, or a fixed region of the
desktop. The second option will instead capture the contents of a single
window, regardless of its position on the screen.
For example, to grab the entire desktop using @command{ffmpeg}:
@example
ffmpeg -f gdigrab -framerate 6 -i desktop out.mpg
@end example
Grab a 640x480 region at position @code{10,20}:
@example
ffmpeg -f gdigrab -framerate 6 -offset_x 10 -offset_y 20 -video_size vga -i desktop out.mpg
@end example
Grab the contents of the window named "Calculator"
@example
ffmpeg -f gdigrab -framerate 6 -i title=Calculator out.mpg
@end example
@subsection Options
@table @option
@item draw_mouse
Specify whether to draw the mouse pointer. Use the value @code{0} to
not draw the pointer. Default value is @code{1}.
@item framerate
Set the grabbing frame rate. Default value is @code{ntsc},
corresponding to a frame rate of @code{30000/1001}.
@item show_region
Show grabbed region on screen.
If @var{show_region} is specified with @code{1}, then the grabbing
region will be indicated on screen. With this option, it is easy to
know what is being grabbed if only a portion of the screen is grabbed.
Note that @var{show_region} is incompatible with grabbing the contents
of a single window.
For example:
@example
ffmpeg -f gdigrab -show_region 1 -framerate 6 -video_size cif -offset_x 10 -offset_y 20 -i desktop out.mpg
@end example
@item video_size
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.
@item offset_x
When capturing a region with @var{video_size}, set the distance from the left edge of the screen or desktop.
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.
@item offset_y
When capturing a region with @var{video_size}, set the distance from the top edge of the screen or desktop.
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.
@end table
@section iec61883 @section iec61883
FireWire DV/HDV input device using libiec61883. FireWire DV/HDV input device using libiec61883.

@ -26,6 +26,7 @@ OBJS-$(CONFIG_FBDEV_INDEV) += fbdev_dec.o \
fbdev_common.o fbdev_common.o
OBJS-$(CONFIG_FBDEV_OUTDEV) += fbdev_enc.o \ OBJS-$(CONFIG_FBDEV_OUTDEV) += fbdev_enc.o \
fbdev_common.o fbdev_common.o
OBJS-$(CONFIG_GDIGRAB_INDEV) += gdigrab.o
OBJS-$(CONFIG_IEC61883_INDEV) += iec61883.o OBJS-$(CONFIG_IEC61883_INDEV) += iec61883.o
OBJS-$(CONFIG_JACK_INDEV) += jack_audio.o timefilter.o OBJS-$(CONFIG_JACK_INDEV) += jack_audio.o timefilter.o
OBJS-$(CONFIG_LAVFI_INDEV) += lavfi.o OBJS-$(CONFIG_LAVFI_INDEV) += lavfi.o

@ -53,6 +53,7 @@ void avdevice_register_all(void)
REGISTER_INDEV (DSHOW, dshow); REGISTER_INDEV (DSHOW, dshow);
REGISTER_INDEV (DV1394, dv1394); REGISTER_INDEV (DV1394, dv1394);
REGISTER_INOUTDEV(FBDEV, fbdev); REGISTER_INOUTDEV(FBDEV, fbdev);
REGISTER_INDEV (GDIGRAB, gdigrab);
REGISTER_INDEV (IEC61883, iec61883); REGISTER_INDEV (IEC61883, iec61883);
REGISTER_INDEV (JACK, jack); REGISTER_INDEV (JACK, jack);
REGISTER_INDEV (LAVFI, lavfi); REGISTER_INDEV (LAVFI, lavfi);

@ -0,0 +1,630 @@
/*
* GDI video grab interface
*
* This file is part of FFmpeg.
*
* Copyright (C) 2013 Calvin Walton <calvin.walton@kepstin.ca>
* Copyright (C) 2007-2010 Christophe Gisquet <word1.word2@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
* GDI frame device demuxer
* @author Calvin Walton <calvin.walton@kepstin.ca>
* @author Christophe Gisquet <word1.word2@gmail.com>
*/
#include "config.h"
#include "libavformat/internal.h"
#include "libavutil/opt.h"
#include "libavutil/time.h"
#include <windows.h>
/**
* GDI Device Demuxer context
*/
struct gdigrab {
const AVClass *class; /**< Class for private options */
int frame_size; /**< Size in bytes of the frame pixel data */
int header_size; /**< Size in bytes of the DIB header */
AVRational time_base; /**< Time base */
int64_t time_frame; /**< Current time */
int draw_mouse; /**< Draw mouse cursor (private option) */
int show_region; /**< Draw border (private option) */
AVRational framerate; /**< Capture framerate (private option) */
int width; /**< Width of the grab frame (private option) */
int height; /**< Height of the grab frame (private option) */
int offset_x; /**< Capture x offset (private option) */
int offset_y; /**< Capture y offset (private option) */
HWND hwnd; /**< Handle of the window for the grab */
HDC source_hdc; /**< Source device context */
HDC dest_hdc; /**< Destination, source-compatible DC */
BITMAPINFO bmi; /**< Information describing DIB format */
HBITMAP hbmp; /**< Information on the bitmap captured */
void *buffer; /**< The buffer containing the bitmap image data */
RECT clip_rect; /**< The subarea of the screen or window to clip */
HWND region_hwnd; /**< Handle of the region border window */
int cursor_error_printed;
};
#define WIN32_API_ERROR(str) \
av_log(s1, AV_LOG_ERROR, str " (error %li)\n", GetLastError())
#define REGION_WND_BORDER 3
/**
* Callback to handle Windows messages for the region outline window.
*
* In particular, this handles painting the frame rectangle.
*
* @param hwnd The region outline window handle.
* @param msg The Windows message.
* @param wparam First Windows message parameter.
* @param lparam Second Windows message parameter.
* @return 0 success, !0 failure
*/
static LRESULT CALLBACK
gdigrab_region_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
PAINTSTRUCT ps;
HDC hdc;
RECT rect;
switch (msg) {
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
FrameRect(hdc, &rect, GetStockObject(BLACK_BRUSH));
rect.left++; rect.top++; rect.right--; rect.bottom--;
FrameRect(hdc, &rect, GetStockObject(WHITE_BRUSH));
rect.left++; rect.top++; rect.right--; rect.bottom--;
FrameRect(hdc, &rect, GetStockObject(BLACK_BRUSH));
EndPaint(hwnd, &ps);
break;
default:
return DefWindowProc(hwnd, msg, wparam, lparam);
}
return 0;
}
/**
* Initialize the region outline window.
*
* @param s1 The format context.
* @param gdigrab gdigrab context.
* @return 0 success, !0 failure
*/
static int
gdigrab_region_wnd_init(AVFormatContext *s1, struct gdigrab *gdigrab)
{
HWND hwnd;
RECT rect = gdigrab->clip_rect;
HRGN region = NULL;
HRGN region_interior = NULL;
DWORD style = WS_POPUP | WS_VISIBLE;
DWORD ex = WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_TRANSPARENT;
rect.left -= REGION_WND_BORDER; rect.top -= REGION_WND_BORDER;
rect.right += REGION_WND_BORDER; rect.bottom += REGION_WND_BORDER;
AdjustWindowRectEx(&rect, style, FALSE, ex);
// Create a window with no owner; use WC_DIALOG instead of writing a custom
// window class
hwnd = CreateWindowEx(ex, WC_DIALOG, NULL, style, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
NULL, NULL, NULL, NULL);
if (!hwnd) {
WIN32_API_ERROR("Could not create region display window");
goto error;
}
// Set the window shape to only include the border area
GetClientRect(hwnd, &rect);
region = CreateRectRgn(0, 0,
rect.right - rect.left, rect.bottom - rect.top);
region_interior = CreateRectRgn(REGION_WND_BORDER, REGION_WND_BORDER,
rect.right - rect.left - REGION_WND_BORDER,
rect.bottom - rect.top - REGION_WND_BORDER);
CombineRgn(region, region, region_interior, RGN_DIFF);
if (!SetWindowRgn(hwnd, region, FALSE)) {
WIN32_API_ERROR("Could not set window region");
goto error;
}
// The "region" memory is now owned by the window
region = NULL;
DeleteObject(region_interior);
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR) gdigrab_region_wnd_proc);
ShowWindow(hwnd, SW_SHOW);
gdigrab->region_hwnd = hwnd;
return 0;
error:
if (region)
DeleteObject(region);
if (region_interior)
DeleteObject(region_interior);
if (hwnd)
DestroyWindow(hwnd);
return 1;
}
/**
* Cleanup/free the region outline window.
*
* @param s1 The format context.
* @param gdigrab gdigrab context.
*/
static void
gdigrab_region_wnd_destroy(AVFormatContext *s1, struct gdigrab *gdigrab)
{
if (gdigrab->region_hwnd)
DestroyWindow(gdigrab->region_hwnd);
gdigrab->region_hwnd = NULL;
}
/**
* Process the Windows message queue.
*
* This is important to prevent Windows from thinking the window has become
* unresponsive. As well, things like WM_PAINT (to actually draw the window
* contents) are handled from the message queue context.
*
* @param s1 The format context.
* @param gdigrab gdigrab context.
*/
static void
gdigrab_region_wnd_update(AVFormatContext *s1, struct gdigrab *gdigrab)
{
HWND hwnd = gdigrab->region_hwnd;
MSG msg;
while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)) {
DispatchMessage(&msg);
}
}
/**
* Initializes the gdi grab device demuxer (public device demuxer API).
*
* @param s1 Context from avformat core
* @return AVERROR_IO error, 0 success
*/
static int
gdigrab_read_header(AVFormatContext *s1)
{
struct gdigrab *gdigrab = s1->priv_data;
HWND hwnd;
HDC source_hdc = NULL;
HDC dest_hdc = NULL;
BITMAPINFO bmi;
HBITMAP hbmp = NULL;
void *buffer = NULL;
const char *filename = s1->filename;
const char *name = NULL;
AVStream *st = NULL;
int bpp;
RECT virtual_rect;
RECT clip_rect;
BITMAP bmp;
int ret;
if (!strncmp(filename, "title=", 6)) {
name = filename + 6;
hwnd = FindWindow(NULL, name);
if (!hwnd) {
av_log(s1, AV_LOG_ERROR,
"Can't find window '%s', aborting.\n", name);
ret = AVERROR(EIO);
goto error;
}
if (gdigrab->show_region) {
av_log(s1, AV_LOG_WARNING,
"Can't show region when grabbing a window.\n");
gdigrab->show_region = 0;
}
} else if (!strcmp(filename, "desktop")) {
hwnd = NULL;
} else {
av_log(s1, AV_LOG_ERROR,
"Please use \"desktop\" or \"title=<windowname>\" to specify your target.\n");
ret = AVERROR(EIO);
goto error;
}
if (hwnd) {
GetClientRect(hwnd, &virtual_rect);
} else {
virtual_rect.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
virtual_rect.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
virtual_rect.right = virtual_rect.left + GetSystemMetrics(SM_CXVIRTUALSCREEN);
virtual_rect.bottom = virtual_rect.top + GetSystemMetrics(SM_CYVIRTUALSCREEN);
}
/* If no width or height set, use full screen/window area */
if (!gdigrab->width || !gdigrab->height) {
clip_rect.left = virtual_rect.left;
clip_rect.top = virtual_rect.top;
clip_rect.right = virtual_rect.right;
clip_rect.bottom = virtual_rect.bottom;
} else {
clip_rect.left = gdigrab->offset_x;
clip_rect.top = gdigrab->offset_y;
clip_rect.right = gdigrab->width + gdigrab->offset_x;
clip_rect.bottom = gdigrab->height + gdigrab->offset_y;
}
if (clip_rect.left < virtual_rect.left ||
clip_rect.top < virtual_rect.top ||
clip_rect.right > virtual_rect.right ||
clip_rect.bottom > virtual_rect.bottom) {
av_log(s1, AV_LOG_ERROR,
"Capture area (%li,%li),(%li,%li) extends outside window area (%li,%li),(%li,%li)",
clip_rect.left, clip_rect.top,
clip_rect.right, clip_rect.bottom,
virtual_rect.left, virtual_rect.top,
virtual_rect.right, virtual_rect.bottom);
ret = AVERROR(EIO);
goto error;
}
/* This will get the device context for the selected window, or if
* none, the primary screen */
source_hdc = GetDC(hwnd);
if (!source_hdc) {
WIN32_API_ERROR("Couldn't get window device context");
ret = AVERROR(EIO);
goto error;
}
bpp = GetDeviceCaps(source_hdc, BITSPIXEL);
if (name) {
av_log(s1, AV_LOG_INFO,
"Found window %s, capturing %lix%lix%i at (%li,%li)\n",
name,
clip_rect.right - clip_rect.left,
clip_rect.bottom - clip_rect.top,
bpp, clip_rect.left, clip_rect.top);
} else {
av_log(s1, AV_LOG_INFO,
"Capturing whole desktop as %lix%lix%i at (%li,%li)\n",
clip_rect.right - clip_rect.left,
clip_rect.bottom - clip_rect.top,
bpp, clip_rect.left, clip_rect.top);
}
if (clip_rect.right - clip_rect.left <= 0 ||
clip_rect.bottom - clip_rect.top <= 0 || bpp%8) {
av_log(s1, AV_LOG_ERROR, "Invalid properties, aborting\n");
ret = AVERROR(EIO);
goto error;
}
dest_hdc = CreateCompatibleDC(source_hdc);
if (!dest_hdc) {
WIN32_API_ERROR("Screen DC CreateCompatibleDC");
ret = AVERROR(EIO);
goto error;
}
/* Create a DIB and select it into the dest_hdc */
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = clip_rect.right - clip_rect.left;
bmi.bmiHeader.biHeight = -(clip_rect.bottom - clip_rect.top);
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = bpp;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = 0;
bmi.bmiHeader.biXPelsPerMeter = 0;
bmi.bmiHeader.biYPelsPerMeter = 0;
bmi.bmiHeader.biClrUsed = 0;
bmi.bmiHeader.biClrImportant = 0;
hbmp = CreateDIBSection(dest_hdc, &bmi, DIB_RGB_COLORS,
&buffer, NULL, 0);
if (!hbmp) {
WIN32_API_ERROR("Creating DIB Section");
ret = AVERROR(EIO);
goto error;
}
if (!SelectObject(dest_hdc, hbmp)) {
WIN32_API_ERROR("SelectObject");
ret = AVERROR(EIO);
goto error;
}
/* Get info from the bitmap */
GetObject(hbmp, sizeof(BITMAP), &bmp);
st = avformat_new_stream(s1, NULL);
if (!st) {
ret = AVERROR(ENOMEM);
goto error;
}
avpriv_set_pts_info(st, 64, 1, 1000000); /* 64 bits pts in us */
gdigrab->frame_size = bmp.bmWidthBytes * bmp.bmHeight * bmp.bmPlanes;
gdigrab->header_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) +
(bpp <= 8 ? (1 << bpp) : 0) * sizeof(RGBQUAD) /* palette size */;
gdigrab->time_base = av_inv_q(gdigrab->framerate);
gdigrab->time_frame = av_gettime() / av_q2d(gdigrab->time_base);
gdigrab->hwnd = hwnd;
gdigrab->source_hdc = source_hdc;
gdigrab->dest_hdc = dest_hdc;
gdigrab->hbmp = hbmp;
gdigrab->bmi = bmi;
gdigrab->buffer = buffer;
gdigrab->clip_rect = clip_rect;
gdigrab->cursor_error_printed = 0;
if (gdigrab->show_region) {
if (gdigrab_region_wnd_init(s1, gdigrab)) {
ret = AVERROR(EIO);
goto error;
}
}
st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
st->codec->codec_id = AV_CODEC_ID_BMP;
st->codec->time_base = gdigrab->time_base;
st->codec->bit_rate = (gdigrab->header_size + gdigrab->frame_size) * 1/av_q2d(gdigrab->time_base) * 8;
return 0;
error:
if (source_hdc)
ReleaseDC(hwnd, source_hdc);
if (dest_hdc)
DeleteDC(dest_hdc);
if (hbmp)
DeleteObject(hbmp);
if (source_hdc)
DeleteDC(source_hdc);
return ret;
}
/**
* 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, struct gdigrab *gdigrab)
{
CURSORINFO ci = {0};
#define CURSOR_ERROR(str) \
if (!gdigrab->cursor_error_printed) { \
WIN32_API_ERROR(str); \
gdigrab->cursor_error_printed = 1; \
}
ci.cbSize = sizeof(ci);
if (GetCursorInfo(&ci)) {
HCURSOR icon = CopyCursor(ci.hCursor);
ICONINFO info;
POINT pos;
RECT clip_rect = gdigrab->clip_rect;
HWND hwnd = gdigrab->hwnd;
if (ci.flags != CURSOR_SHOWING)
return;
if (!icon) {
/* Use the standard arrow cursor as a fallback.
* You'll probably only hit this in Wine, which can't fetch
* the current system cursor. */
icon = CopyCursor(LoadCursor(NULL, IDC_ARROW));
}
if (!GetIconInfo(icon, &info)) {
CURSOR_ERROR("Could not get icon info");
goto icon_error;
}
pos.x = ci.ptScreenPos.x - clip_rect.left - info.xHotspot;
pos.y = ci.ptScreenPos.y - clip_rect.top - info.yHotspot;
if (hwnd) {
RECT rect;
if (GetWindowRect(hwnd, &rect)) {
pos.x -= rect.left;
pos.y -= rect.top;
} else {
CURSOR_ERROR("Couldn't get window rectangle");
goto icon_error;
}
}
av_log(s1, AV_LOG_DEBUG, "Cursor pos (%li,%li) -> (%li,%li)\n",
ci.ptScreenPos.x, ci.ptScreenPos.y, pos.x, pos.y);
if (pos.x >= 0 && pos.x <= clip_rect.right - clip_rect.left &&
pos.y >= 0 && pos.y <= clip_rect.bottom - clip_rect.top) {
if (!DrawIcon(gdigrab->dest_hdc, pos.x, pos.y, icon))
CURSOR_ERROR("Couldn't draw icon");
}
icon_error:
if (icon)
DestroyCursor(icon);
} else {
CURSOR_ERROR("Couldn't get cursor info");
}
}
/**
* 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)
{
struct gdigrab *gdigrab = s1->priv_data;
HDC dest_hdc = gdigrab->dest_hdc;
HDC source_hdc = gdigrab->source_hdc;
RECT clip_rect = gdigrab->clip_rect;
AVRational time_base = gdigrab->time_base;
int64_t time_frame = gdigrab->time_frame;
BITMAPFILEHEADER bfh;
int file_size = gdigrab->header_size + gdigrab->frame_size;
int64_t curtime, delay;
/* Calculate the time of the next frame */
time_frame += INT64_C(1000000);
/* Run Window message processing queue */
if (gdigrab->show_region)
gdigrab_region_wnd_update(s1, gdigrab);
/* wait based on the frame rate */
for (;;) {
curtime = av_gettime();
delay = time_frame * av_q2d(time_base) - curtime;
if (delay <= 0) {
if (delay < INT64_C(-1000000) * av_q2d(time_base)) {
time_frame += INT64_C(1000000);
}
break;
}
if (s1->flags & AVFMT_FLAG_NONBLOCK) {
return AVERROR(EAGAIN);
} else {
av_usleep(delay);
}
}
if (av_new_packet(pkt, file_size) < 0)
return AVERROR(ENOMEM);
pkt->pts = curtime;
/* Blit screen grab */
if (!BitBlt(dest_hdc, 0, 0,
clip_rect.right - clip_rect.left,
clip_rect.bottom - clip_rect.top,
source_hdc,
clip_rect.left, clip_rect.top, SRCCOPY | CAPTUREBLT)) {
WIN32_API_ERROR("Failed to capture image");
return AVERROR(EIO);
}
if (gdigrab->draw_mouse)
paint_mouse_pointer(s1, gdigrab);
/* Copy bits to packet data */
bfh.bfType = 0x4d42; /* "BM" in little-endian */
bfh.bfSize = file_size;
bfh.bfReserved1 = 0;
bfh.bfReserved2 = 0;
bfh.bfOffBits = gdigrab->header_size;
memcpy(pkt->data, &bfh, sizeof(bfh));
memcpy(pkt->data + sizeof(bfh), &gdigrab->bmi.bmiHeader, sizeof(gdigrab->bmi.bmiHeader));
if (gdigrab->bmi.bmiHeader.biBitCount <= 8)
GetDIBColorTable(dest_hdc, 0, 1 << gdigrab->bmi.bmiHeader.biBitCount,
(RGBQUAD *) (pkt->data + sizeof(bfh) + sizeof(gdigrab->bmi.bmiHeader)));
memcpy(pkt->data + gdigrab->header_size, gdigrab->buffer, gdigrab->frame_size);
gdigrab->time_frame = time_frame;
return gdigrab->header_size + gdigrab->frame_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)
{
struct gdigrab *s = s1->priv_data;
if (s->show_region)
gdigrab_region_wnd_destroy(s1, s);
if (s->source_hdc)
ReleaseDC(s->hwnd, s->source_hdc);
if (s->dest_hdc)
DeleteDC(s->dest_hdc);
if (s->hbmp)
DeleteObject(s->hbmp);
if (s->source_hdc)
DeleteDC(s->source_hdc);
return 0;
}
#define OFFSET(x) offsetof(struct gdigrab, x)
#define DEC AV_OPT_FLAG_DECODING_PARAM
static const AVOption options[] = {
{ "draw_mouse", "draw the mouse pointer", OFFSET(draw_mouse), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, DEC },
{ "show_region", "draw border around capture area", OFFSET(show_region), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, DEC },
{ "framerate", "set video frame rate", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, {.str = "ntsc"}, 0, 0, DEC },
{ "video_size", "set video frame size", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, 0, 0, DEC },
{ "offset_x", "capture area x offset", OFFSET(offset_x), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC },
{ "offset_y", "capture area y offset", OFFSET(offset_y), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC },
{ NULL },
};
static const AVClass gdigrab_class = {
.class_name = "GDIgrab indev",
.item_name = av_default_item_name,
.option = options,
.version = LIBAVUTIL_VERSION_INT,
};
/** gdi grabber device demuxer declaration */
AVInputFormat ff_gdigrab_demuxer = {
.name = "gdigrab",
.long_name = NULL_IF_CONFIG_SMALL("GDI API Windows frame grabber"),
.priv_data_size = sizeof(struct gdigrab),
.read_header = gdigrab_read_header,
.read_packet = gdigrab_read_packet,
.read_close = gdigrab_read_close,
.flags = AVFMT_NOFILE,
.priv_class = &gdigrab_class,
};
Loading…
Cancel
Save