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