mfvideosrc: Add support for DirectShow capture filter
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / sys / mediafoundation / gstmfdevice.cpp
1 /* GStreamer
2  * Copyright (C) 2020 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 "gstmfconfig.h"
25
26 #include "gstmfvideosrc.h"
27 #include "gstmfutils.h"
28 #include "gstmfsourceobject.h"
29
30 #include "gstmfdevice.h"
31
32 #if GST_MF_WINAPI_DESKTOP
33 #include "gstwin32devicewatcher.h"
34 #include "gstmfcapturedshow.h"
35
36 #ifndef INITGUID
37 #include <initguid.h>
38 #endif
39
40 #include <dbt.h>
41 DEFINE_GUID (GST_KSCATEGORY_CAPTURE, 0x65E8773DL, 0x8F56,
42     0x11D0, 0xA3, 0xB9, 0x00, 0xA0, 0xC9, 0x22, 0x31, 0x96);
43 #endif
44
45 #if GST_MF_WINAPI_APP
46 #include <gst/winrt/gstwinrt.h>
47 /* *INDENT-OFF* */
48 using namespace ABI::Windows::Devices::Enumeration;
49 /* *INDENT-ON* */
50 #endif
51
52 GST_DEBUG_CATEGORY_EXTERN (gst_mf_debug);
53 #define GST_CAT_DEFAULT gst_mf_debug
54
55 enum
56 {
57   PROP_0,
58   PROP_DEVICE_PATH,
59 };
60
61 struct _GstMFDevice
62 {
63   GstDevice parent;
64
65   gchar *device_path;
66 };
67
68 G_DEFINE_TYPE (GstMFDevice, gst_mf_device, GST_TYPE_DEVICE);
69
70 static void gst_mf_device_get_property (GObject * object,
71     guint prop_id, GValue * value, GParamSpec * pspec);
72 static void gst_mf_device_set_property (GObject * object,
73     guint prop_id, const GValue * value, GParamSpec * pspec);
74 static void gst_mf_device_finalize (GObject * object);
75 static GstElement *gst_mf_device_create_element (GstDevice * device,
76     const gchar * name);
77
78 static void
79 gst_mf_device_class_init (GstMFDeviceClass * klass)
80 {
81   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
82   GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
83
84   dev_class->create_element = gst_mf_device_create_element;
85
86   gobject_class->get_property = gst_mf_device_get_property;
87   gobject_class->set_property = gst_mf_device_set_property;
88   gobject_class->finalize = gst_mf_device_finalize;
89
90   g_object_class_install_property (gobject_class, PROP_DEVICE_PATH,
91       g_param_spec_string ("device-path", "Device Path",
92           "The device path", nullptr,
93           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
94               G_PARAM_STATIC_STRINGS)));
95 }
96
97 static void
98 gst_mf_device_init (GstMFDevice * self)
99 {
100 }
101
102 static void
103 gst_mf_device_finalize (GObject * object)
104 {
105   GstMFDevice *self = GST_MF_DEVICE (object);
106
107   g_free (self->device_path);
108
109   G_OBJECT_CLASS (gst_mf_device_parent_class)->finalize (object);
110 }
111
112 static GstElement *
113 gst_mf_device_create_element (GstDevice * device, const gchar * name)
114 {
115   GstMFDevice *self = GST_MF_DEVICE (device);
116   GstElement *elem;
117
118   elem = gst_element_factory_make ("mfvideosrc", name);
119
120   g_object_set (elem, "device-path", self->device_path, nullptr);
121
122   return elem;
123 }
124
125 static void
126 gst_mf_device_get_property (GObject * object, guint prop_id,
127     GValue * value, GParamSpec * pspec)
128 {
129   GstMFDevice *self = GST_MF_DEVICE (object);
130
131   switch (prop_id) {
132     case PROP_DEVICE_PATH:
133       g_value_set_string (value, self->device_path);
134       break;
135     default:
136       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
137       break;
138   }
139 }
140
141 static void
142 gst_mf_device_set_property (GObject * object, guint prop_id,
143     const GValue * value, GParamSpec * pspec)
144 {
145   GstMFDevice *self = GST_MF_DEVICE (object);
146
147   switch (prop_id) {
148     case PROP_DEVICE_PATH:
149       self->device_path = g_value_dup_string (value);
150       break;
151     default:
152       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
153       break;
154   }
155 }
156
157 struct _GstMFDeviceProvider
158 {
159   GstDeviceProvider parent;
160
161   GstObject *watcher;
162
163   GMutex lock;
164   GCond cond;
165
166   gboolean enum_completed;
167 };
168
169 G_DEFINE_TYPE (GstMFDeviceProvider, gst_mf_device_provider,
170     GST_TYPE_DEVICE_PROVIDER);
171
172 static void gst_mf_device_provider_dispose (GObject * object);
173 static void gst_mf_device_provider_finalize (GObject * object);
174
175 static GList *gst_mf_device_provider_probe (GstDeviceProvider * provider);
176 static gboolean gst_mf_device_provider_start (GstDeviceProvider * provider);
177 static void gst_mf_device_provider_stop (GstDeviceProvider * provider);
178
179 #if GST_MF_WINAPI_DESKTOP
180 static gboolean gst_mf_device_provider_start_win32 (GstDeviceProvider * self);
181 static void gst_mf_device_provider_device_changed (GstWin32DeviceWatcher *
182     watcher, WPARAM wparam, LPARAM lparam, gpointer user_data);
183 #endif
184
185 #if GST_MF_WINAPI_APP
186 static gboolean gst_mf_device_provider_start_winrt (GstDeviceProvider * self);
187 static void
188 gst_mf_device_provider_device_added (GstWinRTDeviceWatcher * watcher,
189     IDeviceInformation * info, gpointer user_data);
190 static void
191 gst_mf_device_provider_device_updated (GstWinRTDeviceWatcher * watcher,
192     IDeviceInformationUpdate * info_update, gpointer user_data);
193 static void gst_mf_device_provider_device_removed (GstWinRTDeviceWatcher *
194     watcher, IDeviceInformationUpdate * info_update, gpointer user_data);
195 static void
196 gst_mf_device_provider_device_enum_completed (GstWinRTDeviceWatcher *
197     watcher, gpointer user_data);
198 #endif
199
200 static void
201 gst_mf_device_provider_on_device_updated (GstMFDeviceProvider * self);
202
203 static void
204 gst_mf_device_provider_class_init (GstMFDeviceProviderClass * klass)
205 {
206   GstDeviceProviderClass *provider_class = GST_DEVICE_PROVIDER_CLASS (klass);
207
208   provider_class->probe = GST_DEBUG_FUNCPTR (gst_mf_device_provider_probe);
209   provider_class->start = GST_DEBUG_FUNCPTR (gst_mf_device_provider_start);
210   provider_class->stop = GST_DEBUG_FUNCPTR (gst_mf_device_provider_stop);
211
212   gst_device_provider_class_set_static_metadata (provider_class,
213       "Media Foundation Device Provider",
214       "Source/Video", "List Media Foundation source devices",
215       "Seungha Yang <seungha@centricular.com>");
216 }
217
218 static void
219 gst_mf_device_provider_init (GstMFDeviceProvider * self)
220 {
221 #if GST_MF_WINAPI_DESKTOP
222   GstWin32DeviceWatcherCallbacks win32_callbacks;
223
224   win32_callbacks.device_changed = gst_mf_device_provider_device_changed;
225   self->watcher = (GstObject *)
226       gst_win32_device_watcher_new (DBT_DEVTYP_DEVICEINTERFACE,
227       &GST_KSCATEGORY_CAPTURE, &win32_callbacks, self);
228 #endif
229 #if GST_MF_WINAPI_APP
230   if (!self->watcher) {
231     GstWinRTDeviceWatcherCallbacks winrt_callbacks;
232     winrt_callbacks.added = gst_mf_device_provider_device_added;
233     winrt_callbacks.updated = gst_mf_device_provider_device_updated;
234     winrt_callbacks.removed = gst_mf_device_provider_device_removed;
235     winrt_callbacks.enumeration_completed =
236         gst_mf_device_provider_device_enum_completed;
237
238     self->watcher = (GstObject *)
239         gst_winrt_device_watcher_new (GST_WINRT_DEVICE_CLASS_VIDEO_CAPTURE,
240         &winrt_callbacks, self);
241   }
242 #endif
243
244   g_mutex_init (&self->lock);
245   g_cond_init (&self->cond);
246 }
247
248 static void
249 gst_mf_device_provider_dispose (GObject * object)
250 {
251   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (object);
252
253   gst_clear_object (&self->watcher);
254
255   G_OBJECT_CLASS (gst_mf_device_provider_parent_class)->dispose (object);
256 }
257
258 static void
259 gst_mf_device_provider_finalize (GObject * object)
260 {
261   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (object);
262
263   g_mutex_clear (&self->lock);
264   g_cond_clear (&self->cond);
265
266   G_OBJECT_CLASS (gst_mf_device_provider_parent_class)->finalize (object);
267 }
268
269 static void
270 gst_mf_device_provider_probe_internal (GstDeviceProvider * provider,
271     gboolean try_dshow, GList ** list)
272 {
273   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (provider);
274   gint i;
275
276   for (i = 0;; i++) {
277     GstMFSourceObject *obj = nullptr;
278     GstDevice *device;
279     GstStructure *props = nullptr;
280     GstCaps *caps = nullptr;
281     gchar *device_name = nullptr;
282     gchar *device_path = nullptr;
283
284 #if GST_MF_WINAPI_DESKTOP
285     if (try_dshow) {
286       obj = gst_mf_capture_dshow_new (GST_MF_SOURCE_TYPE_VIDEO, i,
287           nullptr, nullptr);
288     } else {
289       obj = gst_mf_source_object_new (GST_MF_SOURCE_TYPE_VIDEO,
290           i, nullptr, nullptr, nullptr);
291     }
292 #else
293     obj = gst_mf_source_object_new (GST_MF_SOURCE_TYPE_VIDEO,
294         i, nullptr, nullptr, nullptr);
295 #endif
296     if (!obj)
297       break;
298
299     caps = gst_mf_source_object_get_caps (obj);
300     if (!caps) {
301       GST_WARNING_OBJECT (self, "Empty caps for device index %d", i);
302       goto next;
303     }
304
305     g_object_get (obj,
306         "device-path", &device_path, "device-name", &device_name, nullptr);
307
308     if (!device_path) {
309       GST_WARNING_OBJECT (self, "Device path is unavailable");
310       goto next;
311     }
312
313     if (!device_name) {
314       GST_WARNING_OBJECT (self, "Device name is unavailable");
315       goto next;
316     }
317
318     props = gst_structure_new ("mf-proplist",
319         "device.api", G_TYPE_STRING, "mediafoundation",
320         "device.path", G_TYPE_STRING, device_path,
321         "device.name", G_TYPE_STRING, device_name, nullptr);
322
323     device = (GstDevice *) g_object_new (GST_TYPE_MF_DEVICE,
324         "device-path", device_path,
325         "display-name", device_name, "caps", caps,
326         "device-class", "Source/Video", "properties", props, nullptr);
327
328     *list = g_list_append (*list, device);
329
330   next:
331     if (caps)
332       gst_caps_unref (caps);
333     if (props)
334       gst_structure_free (props);
335     g_free (device_path);
336     g_free (device_name);
337     gst_object_unref (obj);
338   }
339 }
340
341 static GList *
342 gst_mf_device_provider_probe (GstDeviceProvider * provider)
343 {
344   GList *list = nullptr;
345
346   gst_mf_device_provider_probe_internal (provider, FALSE, &list);
347 #if GST_MF_WINAPI_DESKTOP
348   gst_mf_device_provider_probe_internal (provider, TRUE, &list);
349 #endif
350
351   return list;
352 }
353
354 #if GST_MF_WINAPI_DESKTOP
355 static gboolean
356 gst_mf_device_provider_start_win32 (GstDeviceProvider * provider)
357 {
358   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (provider);
359   GstWin32DeviceWatcher *watcher;
360   GList *devices = nullptr;
361   GList *iter;
362
363   if (!GST_IS_WIN32_DEVICE_WATCHER (self->watcher))
364     return FALSE;
365
366   GST_DEBUG_OBJECT (self, "Starting Win32 watcher");
367
368   watcher = GST_WIN32_DEVICE_WATCHER (self->watcher);
369
370   devices = gst_mf_device_provider_probe (provider);
371   if (devices) {
372     for (iter = devices; iter; iter = g_list_next (iter)) {
373       gst_device_provider_device_add (provider, GST_DEVICE (iter->data));
374     }
375
376     g_list_free (devices);
377   }
378
379   return gst_win32_device_watcher_start (watcher);
380 }
381 #endif
382
383 #if GST_MF_WINAPI_APP
384 static gboolean
385 gst_mf_device_provider_start_winrt (GstDeviceProvider * provider)
386 {
387   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (provider);
388   GstWinRTDeviceWatcher *watcher;
389   GList *devices = nullptr;
390   GList *iter;
391
392   if (!GST_IS_WINRT_DEVICE_WATCHER (self->watcher))
393     return FALSE;
394
395   GST_DEBUG_OBJECT (self, "Starting WinRT watcher");
396   watcher = GST_WINRT_DEVICE_WATCHER (self->watcher);
397
398   self->enum_completed = FALSE;
399
400   if (!gst_winrt_device_watcher_start (watcher))
401     return FALSE;
402
403   /* Wait for initial enumeration to be completed */
404   g_mutex_lock (&self->lock);
405   while (!self->enum_completed)
406     g_cond_wait (&self->cond, &self->lock);
407
408   devices = gst_mf_device_provider_probe (provider);
409   if (devices) {
410     for (iter = devices; iter; iter = g_list_next (iter)) {
411       gst_device_provider_device_add (provider, GST_DEVICE (iter->data));
412     }
413
414     g_list_free (devices);
415   }
416   g_mutex_unlock (&self->lock);
417
418   return TRUE;
419 }
420 #endif
421
422 static gboolean
423 gst_mf_device_provider_start (GstDeviceProvider * provider)
424 {
425   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (provider);
426   gboolean ret = FALSE;
427
428   if (!self->watcher) {
429     GST_ERROR_OBJECT (self, "DeviceWatcher object wasn't configured");
430     return FALSE;
431   }
432 #if GST_MF_WINAPI_DESKTOP
433   ret = gst_mf_device_provider_start_win32 (provider);
434 #endif
435
436 #if GST_MF_WINAPI_APP
437   if (!ret)
438     ret = gst_mf_device_provider_start_winrt (provider);
439 #endif
440
441   return ret;
442 }
443
444 static void
445 gst_mf_device_provider_stop (GstDeviceProvider * provider)
446 {
447   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (provider);
448
449   if (self->watcher) {
450 #if GST_MF_WINAPI_DESKTOP
451     if (GST_IS_WIN32_DEVICE_WATCHER (self->watcher)) {
452       gst_win32_device_watcher_stop (GST_WIN32_DEVICE_WATCHER (self->watcher));
453     }
454 #endif
455 #if GST_MF_WINAPI_APP
456     if (GST_IS_WINRT_DEVICE_WATCHER (self->watcher)) {
457       gst_winrt_device_watcher_stop (GST_WINRT_DEVICE_WATCHER (self->watcher));
458     }
459 #endif
460   }
461 }
462
463 static gboolean
464 gst_mf_device_is_in_list (GList * list, GstDevice * device)
465 {
466   GList *iter;
467   GstStructure *s;
468   const gchar *device_id;
469   gboolean found = FALSE;
470
471   s = gst_device_get_properties (device);
472   g_assert (s);
473
474   device_id = gst_structure_get_string (s, "device.path");
475   g_assert (device_id);
476
477   for (iter = list; iter; iter = g_list_next (iter)) {
478     GstStructure *other_s;
479     const gchar *other_id;
480
481     other_s = gst_device_get_properties (GST_DEVICE (iter->data));
482     g_assert (other_s);
483
484     other_id = gst_structure_get_string (other_s, "device.path");
485     g_assert (other_id);
486
487     if (g_ascii_strcasecmp (device_id, other_id) == 0) {
488       found = TRUE;
489     }
490
491     gst_structure_free (other_s);
492     if (found)
493       break;
494   }
495
496   gst_structure_free (s);
497
498   return found;
499 }
500
501 static void
502 gst_mf_device_provider_update_devices (GstMFDeviceProvider * self)
503 {
504   GstDeviceProvider *provider = GST_DEVICE_PROVIDER_CAST (self);
505   GList *prev_devices = nullptr;
506   GList *new_devices = nullptr;
507   GList *to_add = nullptr;
508   GList *to_remove = nullptr;
509   GList *iter;
510
511   GST_OBJECT_LOCK (self);
512   prev_devices = g_list_copy_deep (provider->devices,
513       (GCopyFunc) gst_object_ref, nullptr);
514   GST_OBJECT_UNLOCK (self);
515
516   new_devices = gst_mf_device_provider_probe (provider);
517
518   /* Ownership of GstDevice for gst_device_provider_device_add()
519    * and gst_device_provider_device_remove() is a bit complicated.
520    * Remove floating reference here for things to be clear */
521   for (iter = new_devices; iter; iter = g_list_next (iter))
522     gst_object_ref_sink (iter->data);
523
524   /* Check newly added devices */
525   for (iter = new_devices; iter; iter = g_list_next (iter)) {
526     if (!gst_mf_device_is_in_list (prev_devices, GST_DEVICE (iter->data))) {
527       to_add = g_list_prepend (to_add, gst_object_ref (iter->data));
528     }
529   }
530
531   /* Check removed device */
532   for (iter = prev_devices; iter; iter = g_list_next (iter)) {
533     if (!gst_mf_device_is_in_list (new_devices, GST_DEVICE (iter->data))) {
534       to_remove = g_list_prepend (to_remove, gst_object_ref (iter->data));
535     }
536   }
537
538   for (iter = to_remove; iter; iter = g_list_next (iter))
539     gst_device_provider_device_remove (provider, GST_DEVICE (iter->data));
540
541   for (iter = to_add; iter; iter = g_list_next (iter))
542     gst_device_provider_device_add (provider, GST_DEVICE (iter->data));
543
544   if (prev_devices)
545     g_list_free_full (prev_devices, (GDestroyNotify) gst_object_unref);
546
547   if (to_add)
548     g_list_free_full (to_add, (GDestroyNotify) gst_object_unref);
549
550   if (to_remove)
551     g_list_free_full (to_remove, (GDestroyNotify) gst_object_unref);
552 }
553
554 #if GST_MF_WINAPI_DESKTOP
555 static void
556 gst_mf_device_provider_device_changed (GstWin32DeviceWatcher * watcher,
557     WPARAM wparam, LPARAM lparam, gpointer user_data)
558 {
559   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (user_data);
560
561   if (wparam == DBT_DEVICEARRIVAL || wparam == DBT_DEVICEREMOVECOMPLETE) {
562     gst_mf_device_provider_update_devices (self);
563   }
564 }
565 #endif
566
567 #if GST_MF_WINAPI_APP
568 static void
569 gst_mf_device_provider_device_added (GstWinRTDeviceWatcher * watcher,
570     IDeviceInformation * info, gpointer user_data)
571 {
572   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (user_data);
573
574   if (self->enum_completed)
575     gst_mf_device_provider_update_devices (self);
576 }
577
578 static void
579 gst_mf_device_provider_device_removed (GstWinRTDeviceWatcher * watcher,
580     IDeviceInformationUpdate * info_update, gpointer user_data)
581 {
582   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (user_data);
583
584   if (self->enum_completed)
585     gst_mf_device_provider_update_devices (self);
586 }
587
588
589 static void
590 gst_mf_device_provider_device_updated (GstWinRTDeviceWatcher * watcher,
591     IDeviceInformationUpdate * info_update, gpointer user_data)
592 {
593   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (user_data);
594
595   gst_mf_device_provider_update_devices (self);
596 }
597
598 static void
599 gst_mf_device_provider_device_enum_completed (GstWinRTDeviceWatcher *
600     watcher, gpointer user_data)
601 {
602   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (user_data);
603
604   g_mutex_lock (&self->lock);
605   GST_DEBUG_OBJECT (self, "Enumeration completed");
606   self->enum_completed = TRUE;
607   g_cond_signal (&self->cond);
608   g_mutex_unlock (&self->lock);
609 }
610 #endif