gstreamer/sys/winscreencap/gstdx9screencapsrc.c
Florian Zwoch 8960debc24 dx9screencapsrc: add "cursor" option to draw the cursor
Drawing is done via the GDI drawing functions. The cursor is
converted to a monochrome version before drawing. This is because
the GDI drawing functions seem to have undefined behavior with
cursor images including an alpha channel.

I could not find any other reliable way to draw these alpha
channel cursors without producing unwanted artifacts. These type
of cursors were introduced with Window Vista when run with it's
Aero theme.

Also adjust the cursor coordinates when capturing non-primary
screens via the "monitor" option.

https://bugzilla.gnome.org/show_bug.cgi?id=760172
2016-11-02 10:38:59 +02:00

620 lines
19 KiB
C

/* GStreamer
* Copyright (C) 2007 Haakon Sporsheim <hakon.sporsheim@tandberg.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:element-dx9screencapsrc
*
* This element uses DirectX to capture the desktop or a portion of it.
* The default is capturing the whole desktop, but #GstDX9ScreenCapSrc:x,
* #GstDX9ScreenCapSrc:y, #GstDX9ScreenCapSrc:width and
* #GstDX9ScreenCapSrc:height can be used to select a particular region.
* Use #GstDX9ScreenCapSrc:monitor for changing which monitor to capture
* from.
*
* <refsect2>
* <title>Example pipelines</title>
* |[
* gst-launch-1.0 dx9screencapsrc ! videoconvert ! dshowvideosink
* ]| Capture the desktop and display it.
* |[
* gst-launch-1.0 dx9screencapsrc x=100 y=100 width=320 height=240 !
* videoconvert ! dshowvideosink
* ]| Capture a portion of the desktop and display it.
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstdx9screencapsrc.h"
#include <gst/video/video.h>
GST_DEBUG_CATEGORY_STATIC (dx9screencapsrc_debug);
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("BGR"))
);
#define gst_dx9screencapsrc_parent_class parent_class
G_DEFINE_TYPE (GstDX9ScreenCapSrc, gst_dx9screencapsrc, GST_TYPE_PUSH_SRC);
enum
{
PROP_0,
PROP_MONITOR,
PROP_SHOW_CURSOR,
PROP_X_POS,
PROP_Y_POS,
PROP_WIDTH,
PROP_HEIGHT
};
static IDirect3D9 *g_d3d9 = NULL;
/* Fwd. decl. */
static void gst_dx9screencapsrc_dispose (GObject * object);
static void gst_dx9screencapsrc_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_dx9screencapsrc_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
static GstCaps *gst_dx9screencapsrc_fixate (GstBaseSrc * bsrc, GstCaps * caps);
static gboolean gst_dx9screencapsrc_set_caps (GstBaseSrc * bsrc,
GstCaps * caps);
static GstCaps *gst_dx9screencapsrc_get_caps (GstBaseSrc * bsrc,
GstCaps * filter);
static gboolean gst_dx9screencapsrc_start (GstBaseSrc * bsrc);
static gboolean gst_dx9screencapsrc_stop (GstBaseSrc * bsrc);
static gboolean gst_dx9screencapsrc_unlock (GstBaseSrc * bsrc);
static GstFlowReturn gst_dx9screencapsrc_create (GstPushSrc * src,
GstBuffer ** buf);
/* Implementation. */
static void
gst_dx9screencapsrc_class_init (GstDX9ScreenCapSrcClass * klass)
{
GObjectClass *go_class;
GstElementClass *e_class;
GstBaseSrcClass *bs_class;
GstPushSrcClass *ps_class;
go_class = (GObjectClass *) klass;
e_class = (GstElementClass *) klass;
bs_class = (GstBaseSrcClass *) klass;
ps_class = (GstPushSrcClass *) klass;
go_class->dispose = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_dispose);
go_class->set_property = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_set_property);
go_class->get_property = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_get_property);
bs_class->get_caps = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_get_caps);
bs_class->set_caps = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_set_caps);
bs_class->start = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_start);
bs_class->stop = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_stop);
bs_class->unlock = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_unlock);
bs_class->fixate = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_fixate);
ps_class->create = GST_DEBUG_FUNCPTR (gst_dx9screencapsrc_create);
g_object_class_install_property (go_class, PROP_MONITOR,
g_param_spec_int ("monitor", "Monitor",
"Which monitor to use (0 = 1st monitor and default)",
0, G_MAXINT, 0, G_PARAM_READWRITE));
g_object_class_install_property (go_class, PROP_SHOW_CURSOR,
g_param_spec_boolean ("cursor", "Show mouse cursor",
"Whether to show mouse cursor (default off)",
FALSE, G_PARAM_READWRITE));
g_object_class_install_property (go_class, PROP_X_POS,
g_param_spec_int ("x", "X",
"Horizontal coordinate of top left corner for the screen capture "
"area", 0, G_MAXINT, 0, G_PARAM_READWRITE));
g_object_class_install_property (go_class, PROP_Y_POS,
g_param_spec_int ("y", "Y",
"Vertical coordinate of top left corner for the screen capture "
"area", 0, G_MAXINT, 0, G_PARAM_READWRITE));
g_object_class_install_property (go_class, PROP_WIDTH,
g_param_spec_int ("width", "Width",
"Width of screen capture area (0 = maximum)",
0, G_MAXINT, 0, G_PARAM_READWRITE));
g_object_class_install_property (go_class, PROP_HEIGHT,
g_param_spec_int ("height", "Height",
"Height of screen capture area (0 = maximum)",
0, G_MAXINT, 0, G_PARAM_READWRITE));
gst_element_class_add_static_pad_template (e_class, &src_template);
gst_element_class_set_static_metadata (e_class,
"DirectX 9 screen capture source", "Source/Video", "Captures screen",
"Haakon Sporsheim <hakon.sporsheim@tandberg.com>");
GST_DEBUG_CATEGORY_INIT (dx9screencapsrc_debug, "dx9screencapsrc", 0,
"DirectX 9 screen capture source");
}
static void
gst_dx9screencapsrc_init (GstDX9ScreenCapSrc * src)
{
/* Set src element inital values... */
src->surface = NULL;
src->d3d9_device = NULL;
src->capture_x = 0;
src->capture_y = 0;
src->capture_w = 0;
src->capture_h = 0;
src->monitor = 0;
src->show_cursor = FALSE;
src->monitor_info.cbSize = sizeof(MONITORINFO);
gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME);
gst_base_src_set_live (GST_BASE_SRC (src), TRUE);
if (!g_d3d9)
g_d3d9 = Direct3DCreate9 (D3D_SDK_VERSION);
else
IDirect3D9_AddRef (g_d3d9);
}
static void
gst_dx9screencapsrc_dispose (GObject * object)
{
GstDX9ScreenCapSrc *src = GST_DX9SCREENCAPSRC (object);
if (src->surface) {
GST_ERROR_OBJECT (object,
"DX9 surface was not freed in _stop, freeing in _dispose!");
IDirect3DSurface9_Release (src->surface);
src->surface = NULL;
}
if (src->d3d9_device) {
IDirect3DDevice9_Release (src->d3d9_device);
src->d3d9_device = NULL;
}
if (!IDirect3D9_Release (g_d3d9))
g_d3d9 = NULL;
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_dx9screencapsrc_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec)
{
GstDX9ScreenCapSrc *src = GST_DX9SCREENCAPSRC (object);
switch (prop_id) {
case PROP_MONITOR:
src->monitor = g_value_get_int (value);
break;
case PROP_SHOW_CURSOR:
src->show_cursor = g_value_get_boolean (value);
break;
case PROP_X_POS:
src->capture_x = g_value_get_int (value);
break;
case PROP_Y_POS:
src->capture_y = g_value_get_int (value);
break;
case PROP_WIDTH:
src->capture_w = g_value_get_int (value);
break;
case PROP_HEIGHT:
src->capture_h = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
};
}
static void
gst_dx9screencapsrc_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstDX9ScreenCapSrc *src = GST_DX9SCREENCAPSRC (object);
switch (prop_id) {
case PROP_MONITOR:
g_value_set_int (value, src->monitor);
break;
case PROP_SHOW_CURSOR:
g_value_set_boolean (value, src->show_cursor);
break;
case PROP_X_POS:
g_value_set_int (value, src->capture_x);
break;
case PROP_Y_POS:
g_value_set_int (value, src->capture_y);
break;
case PROP_WIDTH:
g_value_set_int (value, src->capture_w);
break;
case PROP_HEIGHT:
g_value_set_int (value, src->capture_h);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
};
}
static GstCaps *
gst_dx9screencapsrc_fixate (GstBaseSrc * bsrc, GstCaps * caps)
{
GstStructure *structure;
caps = gst_caps_make_writable (caps);
structure = gst_caps_get_structure (caps, 0);
gst_structure_fixate_field_nearest_int (structure, "width", 640);
gst_structure_fixate_field_nearest_int (structure, "height", 480);
gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1);
caps = GST_BASE_SRC_CLASS (parent_class)->fixate (bsrc, caps);
return caps;
}
static gboolean
gst_dx9screencapsrc_set_caps (GstBaseSrc * bsrc, GstCaps * caps)
{
GstDX9ScreenCapSrc *src = GST_DX9SCREENCAPSRC (bsrc);
GstStructure *structure;
const GValue *framerate;
structure = gst_caps_get_structure (caps, 0);
src->src_rect = src->screen_rect;
if (src->capture_w && src->capture_h) {
src->src_rect.left += src->capture_x;
src->src_rect.top += src->capture_y;
src->src_rect.right = src->src_rect.left + src->capture_w;
src->src_rect.bottom = src->src_rect.top + src->capture_h;
}
framerate = gst_structure_get_value (structure, "framerate");
if (framerate) {
src->rate_numerator = gst_value_get_fraction_numerator (framerate);
src->rate_denominator = gst_value_get_fraction_denominator (framerate);
}
GST_DEBUG_OBJECT (src, "size %dx%d, %d/%d fps",
(gint) (src->src_rect.right - src->src_rect.left),
(gint) (src->src_rect.bottom - src->src_rect.top),
src->rate_numerator, src->rate_denominator);
return TRUE;
}
static GstCaps *
gst_dx9screencapsrc_get_caps (GstBaseSrc * bsrc, GstCaps * filter)
{
GstDX9ScreenCapSrc *src = GST_DX9SCREENCAPSRC (bsrc);
RECT rect_dst;
GstCaps *caps;
if (src->monitor >= IDirect3D9_GetAdapterCount (g_d3d9)) {
GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND,
("Specified monitor with index %d not found", src->monitor), (NULL));
return NULL;
}
if (FAILED (IDirect3D9_GetAdapterDisplayMode (g_d3d9, src->monitor,
&src->disp_mode))) {
return NULL;
}
SetRect (&rect_dst, 0, 0, src->disp_mode.Width, src->disp_mode.Height);
src->screen_rect = rect_dst;
if (src->capture_w && src->capture_h &&
src->capture_x + src->capture_w < rect_dst.right - rect_dst.left &&
src->capture_y + src->capture_h < rect_dst.bottom - rect_dst.top) {
rect_dst.left = src->capture_x;
rect_dst.top = src->capture_y;
rect_dst.right = src->capture_x + src->capture_w;
rect_dst.bottom = src->capture_y + src->capture_h;
} else {
/* Default values */
src->capture_x = src->capture_y = 0;
src->capture_w = src->capture_h = 0;
}
/* Note:
* Expose as xRGB even though the Surface is allocated as ARGB!
* This is due to IDirect3DDevice9_GetFrontBufferData which only takes
* ARGB surface, but the A channel is in reality never used.
* I should add that I had problems specifying ARGB. It might be a bug
* in ffmpegcolorspace which I used for testing.
* Another interesting thing is that directdrawsink did not support ARGB,
* but only xRGB. (On my system, using 32b color depth) And according to
* the DirectX documentation ARGB is NOT a valid display buffer format,
* but xRGB is.
*/
caps = gst_caps_new_simple ("video/x-raw",
"format", G_TYPE_STRING, "BGRx",
"width", G_TYPE_INT, rect_dst.right - rect_dst.left,
"height", G_TYPE_INT, rect_dst.bottom - rect_dst.top,
"framerate", GST_TYPE_FRACTION_RANGE, 1, 1, G_MAXINT, 1,
"pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL);
if (filter) {
GstCaps *tmp =
gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (caps);
caps = tmp;
}
return caps;
}
static gboolean
gst_dx9screencapsrc_start (GstBaseSrc * bsrc)
{
GstDX9ScreenCapSrc *src = GST_DX9SCREENCAPSRC (bsrc);
D3DPRESENT_PARAMETERS d3dpp;
HMONITOR monitor;
HRESULT res;
src->frame_number = -1;
ZeroMemory (&d3dpp, sizeof (D3DPRESENT_PARAMETERS));
d3dpp.Windowed = TRUE;
d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
d3dpp.BackBufferFormat = src->disp_mode.Format;
d3dpp.BackBufferHeight = src->disp_mode.Height;
d3dpp.BackBufferWidth = src->disp_mode.Width;
d3dpp.BackBufferCount = 1;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = GetDesktopWindow ();
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
if (src->monitor >= IDirect3D9_GetAdapterCount (g_d3d9)) {
GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND,
("Specified monitor with index %d not found", src->monitor), (NULL));
return FALSE;
}
res = IDirect3D9_CreateDevice (g_d3d9, src->monitor, D3DDEVTYPE_HAL,
GetDesktopWindow (), D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &src->d3d9_device);
if (FAILED (res))
return FALSE;
monitor = IDirect3D9_GetAdapterMonitor (g_d3d9, src->monitor);
GetMonitorInfo (monitor, &src->monitor_info);
return
SUCCEEDED (IDirect3DDevice9_CreateOffscreenPlainSurface (src->d3d9_device,
src->disp_mode.Width, src->disp_mode.Height, D3DFMT_A8R8G8B8,
D3DPOOL_SYSTEMMEM, &src->surface, NULL));
}
static gboolean
gst_dx9screencapsrc_stop (GstBaseSrc * bsrc)
{
GstDX9ScreenCapSrc *src = GST_DX9SCREENCAPSRC (bsrc);
if (src->surface) {
IDirect3DSurface9_Release (src->surface);
src->surface = NULL;
}
return TRUE;
}
static gboolean
gst_dx9screencapsrc_unlock (GstBaseSrc * bsrc)
{
GstDX9ScreenCapSrc *src = GST_DX9SCREENCAPSRC (bsrc);
GST_OBJECT_LOCK (src);
if (src->clock_id) {
GST_DEBUG_OBJECT (src, "Waking up waiting clock");
gst_clock_id_unschedule (src->clock_id);
}
GST_OBJECT_UNLOCK (src);
return TRUE;
}
static GstFlowReturn
gst_dx9screencapsrc_create (GstPushSrc * push_src, GstBuffer ** buf)
{
GstDX9ScreenCapSrc *src = GST_DX9SCREENCAPSRC (push_src);
GstBuffer *new_buf;
gint new_buf_size, i;
gint width, height, stride;
GstClock *clock;
GstClockTime buf_time, buf_dur;
D3DLOCKED_RECT locked_rect;
LPBYTE p_dst, p_src;
HRESULT hres;
GstMapInfo map;
guint64 frame_number;
if (G_UNLIKELY (!src->d3d9_device)) {
GST_ELEMENT_ERROR (src, CORE, NEGOTIATION, (NULL),
("format wasn't negotiated before create function"));
return GST_FLOW_NOT_NEGOTIATED;
}
clock = gst_element_get_clock (GST_ELEMENT (src));
if (clock != NULL) {
GstClockTime time, base_time;
/* Calculate sync time. */
time = gst_clock_get_time (clock);
base_time = gst_element_get_base_time (GST_ELEMENT (src));
buf_time = time - base_time;
if (src->rate_numerator) {
frame_number = gst_util_uint64_scale (buf_time,
src->rate_numerator, GST_SECOND * src->rate_denominator);
} else {
frame_number = -1;
}
} else {
buf_time = GST_CLOCK_TIME_NONE;
frame_number = -1;
}
if (frame_number != -1 && frame_number == src->frame_number) {
GstClockID id;
GstClockReturn ret;
/* Need to wait for the next frame */
frame_number += 1;
/* Figure out what the next frame time is */
buf_time = gst_util_uint64_scale (frame_number,
src->rate_denominator * GST_SECOND, src->rate_numerator);
id = gst_clock_new_single_shot_id (clock,
buf_time + gst_element_get_base_time (GST_ELEMENT (src)));
GST_OBJECT_LOCK (src);
src->clock_id = id;
GST_OBJECT_UNLOCK (src);
GST_DEBUG_OBJECT (src, "Waiting for next frame time %" G_GUINT64_FORMAT,
buf_time);
ret = gst_clock_id_wait (id, NULL);
GST_OBJECT_LOCK (src);
gst_clock_id_unref (id);
src->clock_id = NULL;
if (ret == GST_CLOCK_UNSCHEDULED) {
/* Got woken up by the unlock function */
GST_OBJECT_UNLOCK (src);
return GST_FLOW_FLUSHING;
}
GST_OBJECT_UNLOCK (src);
/* Duration is a complete 1/fps frame duration */
buf_dur =
gst_util_uint64_scale_int (GST_SECOND, src->rate_denominator,
src->rate_numerator);
} else if (frame_number != -1) {
GstClockTime next_buf_time;
GST_DEBUG_OBJECT (src, "No need to wait for next frame time %"
G_GUINT64_FORMAT " next frame = %" G_GINT64_FORMAT " prev = %"
G_GINT64_FORMAT, buf_time, frame_number, src->frame_number);
next_buf_time = gst_util_uint64_scale (frame_number + 1,
src->rate_denominator * GST_SECOND, src->rate_numerator);
/* Frame duration is from now until the next expected capture time */
buf_dur = next_buf_time - buf_time;
} else {
buf_dur = GST_CLOCK_TIME_NONE;
}
src->frame_number = frame_number;
height = (src->src_rect.bottom - src->src_rect.top);
width = (src->src_rect.right - src->src_rect.left);
new_buf_size = width * 4 * height;
GST_LOG_OBJECT (src,
"creating buffer of %d bytes with %dx%d image",
new_buf_size, width, height);
/* Do screen capture and put it into buffer...
* Aquire front buffer, and lock it
*/
hres =
IDirect3DDevice9_GetFrontBufferData (src->d3d9_device, 0, src->surface);
if (FAILED (hres)) {
GST_DEBUG_OBJECT (src, "DirectX::GetBackBuffer failed.");
return GST_FLOW_ERROR;
}
if (src->show_cursor) {
CURSORINFO ci;
ci.cbSize = sizeof (CURSORINFO);
GetCursorInfo (&ci);
if (ci.flags & CURSOR_SHOWING) {
ICONINFO ii;
HDC memDC;
GetIconInfo (ci.hCursor, &ii);
if (SUCCEEDED (IDirect3DSurface9_GetDC (src->surface, &memDC))) {
HCURSOR cursor = CopyImage (ci.hCursor, IMAGE_CURSOR, 0, 0,
LR_MONOCHROME | LR_DEFAULTSIZE);
DrawIcon (memDC,
ci.ptScreenPos.x - ii.xHotspot - src->monitor_info.rcMonitor.left,
ci.ptScreenPos.y - ii.yHotspot - src->monitor_info.rcMonitor.top,
cursor);
DestroyCursor (cursor);
IDirect3DSurface9_ReleaseDC (src->surface, memDC);
}
DeleteObject (ii.hbmColor);
DeleteObject (ii.hbmMask);
}
}
hres =
IDirect3DSurface9_LockRect (src->surface, &locked_rect, &(src->src_rect),
D3DLOCK_NO_DIRTY_UPDATE | D3DLOCK_NOSYSLOCK | D3DLOCK_READONLY);
if (FAILED (hres)) {
GST_DEBUG_OBJECT (src, "DirectX::LockRect failed.");
return GST_FLOW_ERROR;
}
new_buf = gst_buffer_new_and_alloc (new_buf_size);
gst_buffer_map (new_buf, &map, GST_MAP_WRITE);
p_dst = (LPBYTE) map.data;
p_src = (LPBYTE) locked_rect.pBits;
stride = width * 4;
for (i = 0; i < height; ++i) {
memcpy (p_dst, p_src, stride);
p_dst += stride;
p_src += locked_rect.Pitch;
}
gst_buffer_unmap (new_buf, &map);
/* Unlock copy of front buffer */
IDirect3DSurface9_UnlockRect (src->surface);
GST_BUFFER_TIMESTAMP (new_buf) = buf_time;
GST_BUFFER_DURATION (new_buf) = buf_dur;
if (clock != NULL)
gst_object_unref (clock);
*buf = new_buf;
return GST_FLOW_OK;
}