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