• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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