1 /* GStreamer
2 * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "gstd3d11screencapturedevice.h"
25 #include <gst/d3d11/gstd3d11.h>
26 #include <gst/video/video.h>
27 #include <wrl.h>
28 #include <string.h>
29 #include <string>
30 #include <locale>
31 #include <codecvt>
32
33 /* *INDENT-OFF* */
34 using namespace Microsoft::WRL;
35 /* *INDENT-ON* */
36
37 GST_DEBUG_CATEGORY_EXTERN (gst_d3d11_screen_capture_device_debug);
38 #define GST_CAT_DEFAULT gst_d3d11_screen_capture_device_debug
39
40 static GstStaticCaps template_caps =
41 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
42 (GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY, "BGRA") ";"
43 GST_VIDEO_CAPS_MAKE ("BGRA"));
44
45 enum
46 {
47 PROP_0,
48 PROP_MONITOR_HANDLE,
49 };
50
51 struct _GstD3D11ScreenCaptureDevice
52 {
53 GstDevice parent;
54
55 HMONITOR monitor_handle;
56 };
57
58 static void gst_d3d11_screen_capture_device_get_property (GObject * object,
59 guint prop_id, GValue * value, GParamSpec * pspec);
60 static void gst_d3d11_screen_capture_device_set_property (GObject * object,
61 guint prop_id, const GValue * value, GParamSpec * pspec);
62 static GstElement *gst_d3d11_screen_capture_device_create_element (GstDevice *
63 device, const gchar * name);
64
65 G_DEFINE_TYPE (GstD3D11ScreenCaptureDevice,
66 gst_d3d11_screen_capture_device, GST_TYPE_DEVICE);
67
68 static void
gst_d3d11_screen_capture_device_class_init(GstD3D11ScreenCaptureDeviceClass * klass)69 gst_d3d11_screen_capture_device_class_init (GstD3D11ScreenCaptureDeviceClass *
70 klass)
71 {
72 GObjectClass *object_class = G_OBJECT_CLASS (klass);
73 GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
74
75 object_class->get_property = gst_d3d11_screen_capture_device_get_property;
76 object_class->set_property = gst_d3d11_screen_capture_device_set_property;
77
78 g_object_class_install_property (object_class, PROP_MONITOR_HANDLE,
79 g_param_spec_uint64 ("monitor-handle", "Monitor Handle",
80 "A HMONITOR handle", 0, G_MAXUINT64, 0,
81 (GParamFlags) (G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE |
82 G_PARAM_CONSTRUCT_ONLY)));
83
84
85 dev_class->create_element = gst_d3d11_screen_capture_device_create_element;
86 }
87
88 static void
gst_d3d11_screen_capture_device_init(GstD3D11ScreenCaptureDevice * self)89 gst_d3d11_screen_capture_device_init (GstD3D11ScreenCaptureDevice * self)
90 {
91 }
92
93 static void
gst_d3d11_screen_capture_device_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)94 gst_d3d11_screen_capture_device_get_property (GObject * object, guint prop_id,
95 GValue * value, GParamSpec * pspec)
96 {
97 GstD3D11ScreenCaptureDevice *self = GST_D3D11_SCREEN_CAPTURE_DEVICE (object);
98
99 switch (prop_id) {
100 case PROP_MONITOR_HANDLE:
101 g_value_set_uint64 (value, (guint64) self->monitor_handle);
102 break;
103 default:
104 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
105 break;
106 }
107 }
108
109 static void
gst_d3d11_screen_capture_device_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)110 gst_d3d11_screen_capture_device_set_property (GObject * object, guint prop_id,
111 const GValue * value, GParamSpec * pspec)
112 {
113 GstD3D11ScreenCaptureDevice *self = GST_D3D11_SCREEN_CAPTURE_DEVICE (object);
114
115 switch (prop_id) {
116 case PROP_MONITOR_HANDLE:
117 self->monitor_handle = (HMONITOR) g_value_get_uint64 (value);
118 break;
119 default:
120 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
121 break;
122 }
123 }
124
125 static GstElement *
gst_d3d11_screen_capture_device_create_element(GstDevice * device,const gchar * name)126 gst_d3d11_screen_capture_device_create_element (GstDevice * device,
127 const gchar * name)
128 {
129 GstD3D11ScreenCaptureDevice *self = GST_D3D11_SCREEN_CAPTURE_DEVICE (device);
130 GstElement *elem;
131
132 elem = gst_element_factory_make ("d3d11screencapturesrc", name);
133
134 g_object_set (elem, "monitor-handle", self->monitor_handle, nullptr);
135
136 return elem;
137 }
138
139 struct _GstD3D11ScreenCaptureDeviceProvider
140 {
141 GstDeviceProvider parent;
142 };
143
144 G_DEFINE_TYPE (GstD3D11ScreenCaptureDeviceProvider,
145 gst_d3d11_screen_capture_device_provider, GST_TYPE_DEVICE_PROVIDER);
146
147 static GList *gst_d3d11_screen_capture_device_provider_probe (GstDeviceProvider
148 * provider);
149
150 static void
gst_d3d11_screen_capture_device_provider_class_init(GstD3D11ScreenCaptureDeviceProviderClass * klass)151 gst_d3d11_screen_capture_device_provider_class_init
152 (GstD3D11ScreenCaptureDeviceProviderClass * klass)
153 {
154 GstDeviceProviderClass *provider_class = GST_DEVICE_PROVIDER_CLASS (klass);
155
156 provider_class->probe =
157 GST_DEBUG_FUNCPTR (gst_d3d11_screen_capture_device_provider_probe);
158
159 gst_device_provider_class_set_static_metadata (provider_class,
160 "Direct3D11 Desktop Capture Device Provider",
161 "Source/Monitor", "List Direct3D11 desktop capture source devices",
162 "Seungha Yang <seungha@centricular.com>");
163 }
164
165 static void
gst_d3d11_screen_capture_device_provider_init(GstD3D11ScreenCaptureDeviceProvider * self)166 gst_d3d11_screen_capture_device_provider_init
167 (GstD3D11ScreenCaptureDeviceProvider * self)
168 {
169 }
170
171 static gboolean
get_monitor_name(const MONITORINFOEXW * info,DISPLAYCONFIG_TARGET_DEVICE_NAME * target)172 get_monitor_name (const MONITORINFOEXW * info,
173 DISPLAYCONFIG_TARGET_DEVICE_NAME * target)
174 {
175 UINT32 num_path = 0;
176 UINT32 num_mode = 0;
177 LONG query_ret;
178
179 memset (target, 0, sizeof (DISPLAYCONFIG_TARGET_DEVICE_NAME));
180
181 query_ret = GetDisplayConfigBufferSizes (QDC_ONLY_ACTIVE_PATHS,
182 &num_path, &num_mode);
183 if (query_ret != ERROR_SUCCESS || num_path == 0 || num_mode == 0)
184 return FALSE;
185
186 DISPLAYCONFIG_PATH_INFO *path_infos = (DISPLAYCONFIG_PATH_INFO *)
187 g_alloca (num_path * sizeof (DISPLAYCONFIG_PATH_INFO));
188 DISPLAYCONFIG_MODE_INFO *mode_infos = (DISPLAYCONFIG_MODE_INFO *)
189 g_alloca (num_mode * sizeof (DISPLAYCONFIG_MODE_INFO));
190
191 query_ret = QueryDisplayConfig (QDC_ONLY_ACTIVE_PATHS, &num_path,
192 path_infos, &num_mode, mode_infos, nullptr);
193 if (query_ret != ERROR_SUCCESS)
194 return FALSE;
195
196 for (UINT32 i = 0; i < num_path; i++) {
197 DISPLAYCONFIG_PATH_INFO *p = &path_infos[i];
198 DISPLAYCONFIG_SOURCE_DEVICE_NAME source;
199
200 memset (&source, 0, sizeof (DISPLAYCONFIG_SOURCE_DEVICE_NAME));
201
202 source.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
203 source.header.size = sizeof (DISPLAYCONFIG_SOURCE_DEVICE_NAME);
204 source.header.adapterId = p->sourceInfo.adapterId;
205 source.header.id = p->sourceInfo.id;
206
207 query_ret = DisplayConfigGetDeviceInfo (&source.header);
208 if (query_ret != ERROR_SUCCESS)
209 continue;
210
211 if (wcscmp (info->szDevice, source.viewGdiDeviceName) != 0)
212 continue;
213
214 DISPLAYCONFIG_TARGET_DEVICE_NAME tmp;
215
216 memset (&tmp, 0, sizeof (DISPLAYCONFIG_TARGET_DEVICE_NAME));
217
218 tmp.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
219 tmp.header.size = sizeof (DISPLAYCONFIG_TARGET_DEVICE_NAME);
220 tmp.header.adapterId = p->sourceInfo.adapterId;
221 tmp.header.id = p->targetInfo.id;
222
223 query_ret = DisplayConfigGetDeviceInfo (&tmp.header);
224 if (query_ret != ERROR_SUCCESS)
225 continue;
226
227 memcpy (target, &tmp, sizeof (DISPLAYCONFIG_TARGET_DEVICE_NAME));
228
229 return TRUE;
230 }
231
232 return FALSE;
233 }
234
235 /* XXX: please bump MinGW toolchain version,
236 * DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY defined in wingdi.h */
237 typedef enum
238 {
239 OUTPUT_TECH_OTHER = -1,
240 OUTPUT_TECH_HD15 = 0,
241 OUTPUT_TECH_SVIDEO = 1,
242 OUTPUT_TECH_COMPOSITE_VIDEO = 2,
243 OUTPUT_TECH_COMPONENT_VIDEO = 3,
244 OUTPUT_TECH_DVI = 4,
245 OUTPUT_TECH_HDMI = 5,
246 OUTPUT_TECH_LVDS = 6,
247 OUTPUT_TECH_D_JPN = 8,
248 OUTPUT_TECH_SDI = 9,
249 OUTPUT_TECH_DISPLAYPORT_EXTERNAL = 10,
250 OUTPUT_TECH_DISPLAYPORT_EMBEDDED = 11,
251 OUTPUT_TECH_UDI_EXTERNAL = 12,
252 OUTPUT_TECH_UDI_EMBEDDED = 13,
253 OUTPUT_TECH_SDTVDONGLE = 14,
254 OUTPUT_TECH_MIRACAST = 15,
255 OUTPUT_TECH_INDIRECT_WIRED = 16,
256 OUTPUT_TECH_INDIRECT_VIRTUAL = 17,
257 OUTPUT_TECH_INTERNAL = 0x80000000,
258 OUTPUT_TECH_FORCE_UINT32 = 0xFFFFFFFF
259 } GST_OUTPUT_TECHNOLOGY;
260
261 static const gchar *
output_tech_to_string(GST_OUTPUT_TECHNOLOGY tech)262 output_tech_to_string (GST_OUTPUT_TECHNOLOGY tech)
263 {
264 switch (tech) {
265 case OUTPUT_TECH_HD15:
266 return "hd15";
267 case OUTPUT_TECH_SVIDEO:
268 return "svideo";
269 case OUTPUT_TECH_COMPOSITE_VIDEO:
270 return "composite-video";
271 case OUTPUT_TECH_DVI:
272 return "dvi";
273 case OUTPUT_TECH_HDMI:
274 return "hdmi";
275 case OUTPUT_TECH_LVDS:
276 return "lvds";
277 case OUTPUT_TECH_D_JPN:
278 return "d-jpn";
279 case OUTPUT_TECH_SDI:
280 return "sdi";
281 case OUTPUT_TECH_DISPLAYPORT_EXTERNAL:
282 return "displayport-external";
283 case OUTPUT_TECH_DISPLAYPORT_EMBEDDED:
284 return "displayport-internal";
285 case OUTPUT_TECH_UDI_EXTERNAL:
286 return "udi-external";
287 case OUTPUT_TECH_UDI_EMBEDDED:
288 return "udi-embedded";
289 case OUTPUT_TECH_SDTVDONGLE:
290 return "sdtv";
291 case OUTPUT_TECH_MIRACAST:
292 return "miracast";
293 case OUTPUT_TECH_INDIRECT_WIRED:
294 return "indirect-wired";
295 case OUTPUT_TECH_INDIRECT_VIRTUAL:
296 return "indirect-virtual";
297 case OUTPUT_TECH_INTERNAL:
298 return "internal";
299 default:
300 break;
301 }
302
303 return "unknown";
304 }
305
306 static GstDevice *
create_device(const DXGI_ADAPTER_DESC * adapter_desc,const DXGI_OUTPUT_DESC * output_desc,const MONITORINFOEXW * minfo,const DEVMODEW * dev_mode,const DISPLAYCONFIG_TARGET_DEVICE_NAME * target)307 create_device (const DXGI_ADAPTER_DESC * adapter_desc,
308 const DXGI_OUTPUT_DESC * output_desc,
309 const MONITORINFOEXW * minfo, const DEVMODEW * dev_mode,
310 const DISPLAYCONFIG_TARGET_DEVICE_NAME * target)
311 {
312 GstCaps *caps;
313 gint width, height, left, top, right, bottom;
314 GstStructure *props;
315 std::wstring_convert < std::codecvt_utf8 < wchar_t >, wchar_t >converter;
316 std::string device_name;
317 std::string display_name;
318 std::string device_path;
319 std::string device_description;
320 const gchar *output_type;
321 gboolean primary = FALSE;
322 GstDevice *device;
323
324 left = (gint) dev_mode->dmPosition.x;
325 top = (gint) dev_mode->dmPosition.y;
326 width = dev_mode->dmPelsWidth;
327 height = dev_mode->dmPelsHeight;
328 right = left + width;
329 bottom = top + height;
330
331 caps = gst_static_caps_get (&template_caps);
332 caps = gst_caps_make_writable (caps);
333 gst_caps_set_simple (caps,
334 "width", G_TYPE_INT, width, "height", G_TYPE_INT, height, nullptr);
335
336 device_name = converter.to_bytes (minfo->szDevice);
337 display_name = converter.to_bytes (target->monitorFriendlyDeviceName);
338 device_path = converter.to_bytes (target->monitorDevicePath);
339 device_description = converter.to_bytes (adapter_desc->Description);
340 output_type =
341 output_tech_to_string ((GST_OUTPUT_TECHNOLOGY) target->outputTechnology);
342 if ((minfo->dwFlags & MONITORINFOF_PRIMARY) != 0)
343 primary = TRUE;
344
345 props = gst_structure_new ("d3d11screencapturedevice-proplist",
346 "device.api", G_TYPE_STRING, "d3d11",
347 "device.name", G_TYPE_STRING, GST_STR_NULL (device_name.c_str ()),
348 "device.path", G_TYPE_STRING, GST_STR_NULL (device_path.c_str ()),
349 "device.primary", G_TYPE_BOOLEAN, primary,
350 "device.type", G_TYPE_STRING, output_type,
351 "device.hmonitor", G_TYPE_UINT64, (guint64) output_desc->Monitor,
352 "device.adapter.luid", G_TYPE_INT64,
353 gst_d3d11_luid_to_int64 (&adapter_desc->AdapterLuid),
354 "device.adapter.description", G_TYPE_STRING,
355 GST_STR_NULL (device_description.c_str ()),
356 "desktop.coordinates.left", G_TYPE_INT,
357 (gint) output_desc->DesktopCoordinates.left,
358 "desktop.coordinates.top", G_TYPE_INT,
359 (gint) output_desc->DesktopCoordinates.top,
360 "desktop.coordinates.right", G_TYPE_INT,
361 (gint) output_desc->DesktopCoordinates.right,
362 "desktop.coordinates.bottom", G_TYPE_INT,
363 (gint) output_desc->DesktopCoordinates.bottom,
364 "display.coordinates.left", G_TYPE_INT, left,
365 "display.coordinates.top", G_TYPE_INT, top,
366 "display.coordinates.right", G_TYPE_INT, right,
367 "display.coordinates.bottom", G_TYPE_INT, bottom, nullptr);
368
369 device = (GstDevice *) g_object_new (GST_TYPE_D3D11_SCREEN_CAPTURE_DEVICE,
370 "display-name", display_name.c_str (), "caps", caps, "device-class",
371 "Source/Monitor", "properties", props, "monitor-handle",
372 (guint64) output_desc->Monitor, nullptr);
373
374 gst_caps_unref (caps);
375
376 return device;
377 }
378
379 static GList *
gst_d3d11_screen_capture_device_provider_probe(GstDeviceProvider * provider)380 gst_d3d11_screen_capture_device_provider_probe (GstDeviceProvider * provider)
381 {
382 GList *devices = nullptr;
383 ComPtr < IDXGIFactory1 > factory;
384 HRESULT hr = S_OK;
385
386 hr = CreateDXGIFactory1 (IID_PPV_ARGS (&factory));
387 if (FAILED (hr))
388 return nullptr;
389
390 for (UINT adapter_idx = 0;; adapter_idx++) {
391 ComPtr < IDXGIAdapter1 > adapter;
392 DXGI_ADAPTER_DESC adapter_desc;
393
394 hr = factory->EnumAdapters1 (adapter_idx, &adapter);
395 if (FAILED (hr))
396 break;
397
398 hr = adapter->GetDesc (&adapter_desc);
399 if (FAILED (hr))
400 continue;
401
402 for (UINT output_idx = 0;; output_idx++) {
403 ComPtr < IDXGIOutput > output;
404 ComPtr < IDXGIOutput1 > output1;
405 DXGI_OUTPUT_DESC desc;
406 MONITORINFOEXW minfo;
407 DEVMODEW dev_mode;
408 DISPLAYCONFIG_TARGET_DEVICE_NAME target;
409 GstDevice *dev;
410
411 hr = adapter->EnumOutputs (output_idx, &output);
412 if (FAILED (hr))
413 break;
414
415 hr = output.As (&output1);
416 if (FAILED (hr))
417 continue;
418
419 hr = output->GetDesc (&desc);
420 if (FAILED (hr))
421 continue;
422
423 minfo.cbSize = sizeof (MONITORINFOEXW);
424 if (!GetMonitorInfoW (desc.Monitor, &minfo))
425 continue;
426
427 dev_mode.dmSize = sizeof (DEVMODEW);
428 dev_mode.dmDriverExtra = sizeof (POINTL);
429 dev_mode.dmFields = DM_POSITION;
430 if (!EnumDisplaySettingsW (minfo.szDevice,
431 ENUM_CURRENT_SETTINGS, &dev_mode)) {
432 continue;
433 }
434
435 /* Human readable monitor name is not always availabe, if it's empty
436 * fill it with generic one */
437 if (!get_monitor_name (&minfo, &target) ||
438 wcslen (target.monitorFriendlyDeviceName) == 0) {
439 wcscpy (target.monitorFriendlyDeviceName, L"Generic PnP Monitor");
440 }
441
442 dev = create_device (&adapter_desc, &desc, &minfo, &dev_mode, &target);
443 devices = g_list_append (devices, dev);
444 }
445 }
446
447 return devices;
448 }
449