• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #endif
47 
48 GST_DEBUG_CATEGORY_EXTERN (gst_mf_debug);
49 #define GST_CAT_DEFAULT gst_mf_debug
50 
51 enum
52 {
53   PROP_0,
54   PROP_DEVICE_PATH,
55 };
56 
57 struct _GstMFDevice
58 {
59   GstDevice parent;
60 
61   gchar *device_path;
62 };
63 
64 G_DEFINE_TYPE (GstMFDevice, gst_mf_device, GST_TYPE_DEVICE);
65 
66 static void gst_mf_device_get_property (GObject * object,
67     guint prop_id, GValue * value, GParamSpec * pspec);
68 static void gst_mf_device_set_property (GObject * object,
69     guint prop_id, const GValue * value, GParamSpec * pspec);
70 static void gst_mf_device_finalize (GObject * object);
71 static GstElement *gst_mf_device_create_element (GstDevice * device,
72     const gchar * name);
73 
74 static void
gst_mf_device_class_init(GstMFDeviceClass * klass)75 gst_mf_device_class_init (GstMFDeviceClass * klass)
76 {
77   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
78   GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
79 
80   dev_class->create_element = gst_mf_device_create_element;
81 
82   gobject_class->get_property = gst_mf_device_get_property;
83   gobject_class->set_property = gst_mf_device_set_property;
84   gobject_class->finalize = gst_mf_device_finalize;
85 
86   g_object_class_install_property (gobject_class, PROP_DEVICE_PATH,
87       g_param_spec_string ("device-path", "Device Path",
88           "The device path", NULL,
89           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
90 }
91 
92 static void
gst_mf_device_init(GstMFDevice * self)93 gst_mf_device_init (GstMFDevice * self)
94 {
95 }
96 
97 static void
gst_mf_device_finalize(GObject * object)98 gst_mf_device_finalize (GObject * object)
99 {
100   GstMFDevice *self = GST_MF_DEVICE (object);
101 
102   g_free (self->device_path);
103 
104   G_OBJECT_CLASS (gst_mf_device_parent_class)->finalize (object);
105 }
106 
107 static GstElement *
gst_mf_device_create_element(GstDevice * device,const gchar * name)108 gst_mf_device_create_element (GstDevice * device, const gchar * name)
109 {
110   GstMFDevice *self = GST_MF_DEVICE (device);
111   GstElement *elem;
112 
113   elem = gst_element_factory_make ("mfvideosrc", name);
114 
115   g_object_set (elem, "device-path", self->device_path, NULL);
116 
117   return elem;
118 }
119 
120 static void
gst_mf_device_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)121 gst_mf_device_get_property (GObject * object, guint prop_id,
122     GValue * value, GParamSpec * pspec)
123 {
124   GstMFDevice *self = GST_MF_DEVICE (object);
125 
126   switch (prop_id) {
127     case PROP_DEVICE_PATH:
128       g_value_set_string (value, self->device_path);
129       break;
130     default:
131       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
132       break;
133   }
134 }
135 
136 static void
gst_mf_device_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)137 gst_mf_device_set_property (GObject * object, guint prop_id,
138     const GValue * value, GParamSpec * pspec)
139 {
140   GstMFDevice *self = GST_MF_DEVICE (object);
141 
142   switch (prop_id) {
143     case PROP_DEVICE_PATH:
144       self->device_path = g_value_dup_string (value);
145       break;
146     default:
147       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
148       break;
149   }
150 }
151 
152 struct _GstMFDeviceProvider
153 {
154   GstDeviceProvider parent;
155 
156   GstObject *watcher;
157 
158   GMutex lock;
159   GCond cond;
160 
161   gboolean enum_completed;
162 };
163 
164 G_DEFINE_TYPE (GstMFDeviceProvider, gst_mf_device_provider,
165     GST_TYPE_DEVICE_PROVIDER);
166 
167 static void gst_mf_device_provider_dispose (GObject * object);
168 static void gst_mf_device_provider_finalize (GObject * object);
169 
170 static GList *gst_mf_device_provider_probe (GstDeviceProvider * provider);
171 static gboolean gst_mf_device_provider_start (GstDeviceProvider * provider);
172 static void gst_mf_device_provider_stop (GstDeviceProvider * provider);
173 
174 #if GST_MF_WINAPI_DESKTOP
175 static gboolean gst_mf_device_provider_start_win32 (GstDeviceProvider * self);
176 static void gst_mf_device_provider_device_changed (GstWin32DeviceWatcher *
177     watcher, WPARAM wparam, LPARAM lparam, gpointer user_data);
178 #endif
179 
180 #if GST_MF_WINAPI_APP
181 static gboolean gst_mf_device_provider_start_winrt (GstDeviceProvider * self);
182 static void
183 gst_mf_device_provider_device_added (GstWinRTDeviceWatcher * watcher,
184     __x_ABI_CWindows_CDevices_CEnumeration_CIDeviceInformation * info,
185     gpointer user_data);
186 static void
187 gst_mf_device_provider_device_updated (GstWinRTDeviceWatcher * watcher,
188     __x_ABI_CWindows_CDevices_CEnumeration_CIDeviceInformationUpdate *
189     info_update, gpointer user_data);
190 static void gst_mf_device_provider_device_removed (GstWinRTDeviceWatcher *
191     watcher,
192     __x_ABI_CWindows_CDevices_CEnumeration_CIDeviceInformationUpdate *
193     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
gst_mf_device_provider_class_init(GstMFDeviceProviderClass * klass)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
gst_mf_device_provider_init(GstMFDeviceProvider * self)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
gst_mf_device_provider_dispose(GObject * object)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
gst_mf_device_provider_finalize(GObject * object)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 *
gst_mf_device_provider_probe(GstDeviceProvider * provider)269 gst_mf_device_provider_probe (GstDeviceProvider * provider)
270 {
271   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (provider);
272   GList *list = NULL;
273   gint i;
274 
275   for (i = 0;; i++) {
276     GstMFSourceObject *obj = NULL;
277     GstDevice *device;
278     GstStructure *props = NULL;
279     GstCaps *caps = NULL;
280     gchar *device_name = NULL;
281     gchar *device_path = NULL;
282 
283     obj = gst_mf_source_object_new (GST_MF_SOURCE_TYPE_VIDEO,
284         i, NULL, NULL, NULL);
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, NULL);
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, NULL);
311 
312     device = g_object_new (GST_TYPE_MF_DEVICE, "device-path", device_path,
313         "display-name", device_name, "caps", caps,
314         "device-class", "Source/Video", "properties", props, NULL);
315 
316     list = g_list_append (list, device);
317 
318   next:
319     if (caps)
320       gst_caps_unref (caps);
321     if (props)
322       gst_structure_free (props);
323     g_free (device_path);
324     g_free (device_name);
325     gst_object_unref (obj);
326   }
327 
328   return list;
329 }
330 
331 #if GST_MF_WINAPI_DESKTOP
332 static gboolean
gst_mf_device_provider_start_win32(GstDeviceProvider * provider)333 gst_mf_device_provider_start_win32 (GstDeviceProvider * provider)
334 {
335   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (provider);
336   GstWin32DeviceWatcher *watcher;
337   GList *devices = NULL;
338   GList *iter;
339 
340   if (!GST_IS_WIN32_DEVICE_WATCHER (self->watcher))
341     return FALSE;
342 
343   GST_DEBUG_OBJECT (self, "Starting Win32 watcher");
344 
345   watcher = GST_WIN32_DEVICE_WATCHER (self->watcher);
346 
347   devices = gst_mf_device_provider_probe (provider);
348   if (devices) {
349     for (iter = devices; iter; iter = g_list_next (iter)) {
350       gst_device_provider_device_add (provider, GST_DEVICE (iter->data));
351     }
352 
353     g_list_free (devices);
354   }
355 
356   return gst_win32_device_watcher_start (watcher);
357 }
358 #endif
359 
360 #if GST_MF_WINAPI_APP
361 static gboolean
gst_mf_device_provider_start_winrt(GstDeviceProvider * provider)362 gst_mf_device_provider_start_winrt (GstDeviceProvider * provider)
363 {
364   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (provider);
365   GstWinRTDeviceWatcher *watcher;
366   GList *devices = NULL;
367   GList *iter;
368 
369   if (!GST_IS_WINRT_DEVICE_WATCHER (self->watcher))
370     return FALSE;
371 
372   GST_DEBUG_OBJECT (self, "Starting WinRT watcher");
373   watcher = GST_WINRT_DEVICE_WATCHER (self->watcher);
374 
375   self->enum_completed = FALSE;
376 
377   if (!gst_winrt_device_watcher_start (watcher))
378     return FALSE;
379 
380   /* Wait for initial enumeration to be completed */
381   g_mutex_lock (&self->lock);
382   while (!self->enum_completed)
383     g_cond_wait (&self->cond, &self->lock);
384 
385   devices = gst_mf_device_provider_probe (provider);
386   if (devices) {
387     for (iter = devices; iter; iter = g_list_next (iter)) {
388       gst_device_provider_device_add (provider, GST_DEVICE (iter->data));
389     }
390 
391     g_list_free (devices);
392   }
393   g_mutex_unlock (&self->lock);
394 
395   return TRUE;
396 }
397 #endif
398 
399 static gboolean
gst_mf_device_provider_start(GstDeviceProvider * provider)400 gst_mf_device_provider_start (GstDeviceProvider * provider)
401 {
402   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (provider);
403   gboolean ret = FALSE;
404 
405   if (!self->watcher) {
406     GST_ERROR_OBJECT (self, "DeviceWatcher object wasn't configured");
407     return FALSE;
408   }
409 #if GST_MF_WINAPI_DESKTOP
410   ret = gst_mf_device_provider_start_win32 (provider);
411 #endif
412 
413 #if GST_MF_WINAPI_APP
414   if (!ret)
415     ret = gst_mf_device_provider_start_winrt (provider);
416 #endif
417 
418   return ret;
419 }
420 
421 static void
gst_mf_device_provider_stop(GstDeviceProvider * provider)422 gst_mf_device_provider_stop (GstDeviceProvider * provider)
423 {
424   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (provider);
425 
426   if (self->watcher) {
427 #if GST_MF_WINAPI_DESKTOP
428     if (GST_IS_WIN32_DEVICE_WATCHER (self->watcher)) {
429       gst_win32_device_watcher_stop (GST_WIN32_DEVICE_WATCHER (self->watcher));
430     }
431 #endif
432 #if GST_MF_WINAPI_APP
433     if (GST_IS_WINRT_DEVICE_WATCHER (self->watcher)) {
434       gst_winrt_device_watcher_stop (GST_WINRT_DEVICE_WATCHER (self->watcher));
435     }
436 #endif
437   }
438 }
439 
440 static gboolean
gst_mf_device_is_in_list(GList * list,GstDevice * device)441 gst_mf_device_is_in_list (GList * list, GstDevice * device)
442 {
443   GList *iter;
444   GstStructure *s;
445   const gchar *device_id;
446   gboolean found = FALSE;
447 
448   s = gst_device_get_properties (device);
449   g_assert (s);
450 
451   device_id = gst_structure_get_string (s, "device.path");
452   g_assert (device_id);
453 
454   for (iter = list; iter; iter = g_list_next (iter)) {
455     GstStructure *other_s;
456     const gchar *other_id;
457 
458     other_s = gst_device_get_properties (GST_DEVICE (iter->data));
459     g_assert (other_s);
460 
461     other_id = gst_structure_get_string (other_s, "device.path");
462     g_assert (other_id);
463 
464     if (g_ascii_strcasecmp (device_id, other_id) == 0) {
465       found = TRUE;
466     }
467 
468     gst_structure_free (other_s);
469     if (found)
470       break;
471   }
472 
473   gst_structure_free (s);
474 
475   return found;
476 }
477 
478 static void
gst_mf_device_provider_update_devices(GstMFDeviceProvider * self)479 gst_mf_device_provider_update_devices (GstMFDeviceProvider * self)
480 {
481   GstDeviceProvider *provider = GST_DEVICE_PROVIDER_CAST (self);
482   GList *prev_devices = NULL;
483   GList *new_devices = NULL;
484   GList *to_add = NULL;
485   GList *to_remove = NULL;
486   GList *iter;
487 
488   GST_OBJECT_LOCK (self);
489   prev_devices = g_list_copy_deep (provider->devices,
490       (GCopyFunc) gst_object_ref, NULL);
491   GST_OBJECT_UNLOCK (self);
492 
493   new_devices = gst_mf_device_provider_probe (provider);
494 
495   /* Ownership of GstDevice for gst_device_provider_device_add()
496    * and gst_device_provider_device_remove() is a bit complicated.
497    * Remove floating reference here for things to be clear */
498   for (iter = new_devices; iter; iter = g_list_next (iter))
499     gst_object_ref_sink (iter->data);
500 
501   /* Check newly added devices */
502   for (iter = new_devices; iter; iter = g_list_next (iter)) {
503     if (!gst_mf_device_is_in_list (prev_devices, GST_DEVICE (iter->data))) {
504       to_add = g_list_prepend (to_add, gst_object_ref (iter->data));
505     }
506   }
507 
508   /* Check removed device */
509   for (iter = prev_devices; iter; iter = g_list_next (iter)) {
510     if (!gst_mf_device_is_in_list (new_devices, GST_DEVICE (iter->data))) {
511       to_remove = g_list_prepend (to_remove, gst_object_ref (iter->data));
512     }
513   }
514 
515   for (iter = to_remove; iter; iter = g_list_next (iter))
516     gst_device_provider_device_remove (provider, GST_DEVICE (iter->data));
517 
518   for (iter = to_add; iter; iter = g_list_next (iter))
519     gst_device_provider_device_add (provider, GST_DEVICE (iter->data));
520 
521   if (prev_devices)
522     g_list_free_full (prev_devices, (GDestroyNotify) gst_object_unref);
523 
524   if (to_add)
525     g_list_free_full (to_add, (GDestroyNotify) gst_object_unref);
526 
527   if (to_remove)
528     g_list_free_full (to_remove, (GDestroyNotify) gst_object_unref);
529 }
530 
531 #if GST_MF_WINAPI_DESKTOP
532 static void
gst_mf_device_provider_device_changed(GstWin32DeviceWatcher * watcher,WPARAM wparam,LPARAM lparam,gpointer user_data)533 gst_mf_device_provider_device_changed (GstWin32DeviceWatcher * watcher,
534     WPARAM wparam, LPARAM lparam, gpointer user_data)
535 {
536   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (user_data);
537 
538   if (wparam == DBT_DEVICEARRIVAL || wparam == DBT_DEVICEREMOVECOMPLETE) {
539     gst_mf_device_provider_update_devices (self);
540   }
541 }
542 #endif
543 
544 #if GST_MF_WINAPI_APP
545 static void
gst_mf_device_provider_device_added(GstWinRTDeviceWatcher * watcher,__x_ABI_CWindows_CDevices_CEnumeration_CIDeviceInformation * info,gpointer user_data)546 gst_mf_device_provider_device_added (GstWinRTDeviceWatcher * watcher,
547     __x_ABI_CWindows_CDevices_CEnumeration_CIDeviceInformation * info,
548     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
gst_mf_device_provider_device_removed(GstWinRTDeviceWatcher * watcher,__x_ABI_CWindows_CDevices_CEnumeration_CIDeviceInformationUpdate * info_update,gpointer user_data)557 gst_mf_device_provider_device_removed (GstWinRTDeviceWatcher * watcher,
558     __x_ABI_CWindows_CDevices_CEnumeration_CIDeviceInformationUpdate *
559     info_update, gpointer user_data)
560 {
561   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (user_data);
562 
563   if (self->enum_completed)
564     gst_mf_device_provider_update_devices (self);
565 }
566 
567 
568 static void
gst_mf_device_provider_device_updated(GstWinRTDeviceWatcher * watcher,__x_ABI_CWindows_CDevices_CEnumeration_CIDeviceInformationUpdate * info_update,gpointer user_data)569 gst_mf_device_provider_device_updated (GstWinRTDeviceWatcher * watcher,
570     __x_ABI_CWindows_CDevices_CEnumeration_CIDeviceInformationUpdate *
571     info_update, gpointer user_data)
572 {
573   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (user_data);
574 
575   gst_mf_device_provider_update_devices (self);
576 }
577 
578 static void
gst_mf_device_provider_device_enum_completed(GstWinRTDeviceWatcher * watcher,gpointer user_data)579 gst_mf_device_provider_device_enum_completed (GstWinRTDeviceWatcher *
580     watcher, gpointer user_data)
581 {
582   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (user_data);
583 
584   g_mutex_lock (&self->lock);
585   GST_DEBUG_OBJECT (self, "Enumeration completed");
586   self->enum_completed = TRUE;
587   g_cond_signal (&self->cond);
588   g_mutex_unlock (&self->lock);
589 }
590 #endif
591