• 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 "gstmmdeviceenumerator.h"
25 
26 #ifndef INITGUID
27 #include <initguid.h>
28 #endif
29 
30 /* *INDENT-OFF* */
31 G_BEGIN_DECLS
32 
33 GST_DEBUG_CATEGORY_EXTERN (gst_wasapi_debug);
34 #define GST_CAT_DEFAULT gst_wasapi_debug
35 
36 G_END_DECLS
37 
38 /* IMMNotificationClient implementation */
39 class GstIMMNotificationClient : public IMMNotificationClient
40 {
41 public:
42   static HRESULT
CreateInstance(GstMMDeviceEnumerator * enumerator,const GstMMNotificationClientCallbacks * callbacks,gpointer user_data,IMMNotificationClient ** client)43   CreateInstance (GstMMDeviceEnumerator * enumerator,
44       const GstMMNotificationClientCallbacks * callbacks,
45       gpointer user_data,
46       IMMNotificationClient ** client)
47   {
48     GstIMMNotificationClient *self;
49 
50     self = new GstIMMNotificationClient ();
51 
52     self->callbacks_ = *callbacks;
53     self->user_data_ = user_data;
54     g_weak_ref_set (&self->enumerator_, enumerator);
55 
56     *client = (IMMNotificationClient *) self;
57 
58     return S_OK;
59   }
60 
61   /* IUnknown */
62   STDMETHODIMP
QueryInterface(REFIID riid,void ** object)63   QueryInterface (REFIID riid, void ** object)
64   {
65     if (!object)
66       return E_POINTER;
67 
68     if (riid == IID_IUnknown) {
69       *object = static_cast<IUnknown *> (this);
70     } else if (riid == __uuidof(IMMNotificationClient)) {
71       *object = static_cast<IMMNotificationClient *> (this);
72     } else {
73       *object = nullptr;
74       return E_NOINTERFACE;
75     }
76 
77     AddRef ();
78 
79     return S_OK;
80   }
81 
82   STDMETHODIMP_ (ULONG)
AddRef(void)83   AddRef (void)
84   {
85     GST_TRACE ("%p, %d", this, (guint) ref_count_);
86     return InterlockedIncrement (&ref_count_);
87   }
88 
89   STDMETHODIMP_ (ULONG)
Release(void)90   Release (void)
91   {
92     ULONG ref_count;
93 
94     GST_TRACE ("%p, %d", this, (guint) ref_count_);
95     ref_count = InterlockedDecrement (&ref_count_);
96 
97     if (ref_count == 0) {
98       GST_TRACE ("Delete instance %p", this);
99       delete this;
100     }
101 
102     return ref_count;
103   }
104 
105   /* IMMNotificationClient */
106   STDMETHODIMP
OnDeviceStateChanged(LPCWSTR device_id,DWORD new_state)107   OnDeviceStateChanged (LPCWSTR device_id, DWORD new_state)
108   {
109     GstMMDeviceEnumerator *listener;
110     HRESULT hr;
111 
112     if (!callbacks_.device_state_changed)
113       return S_OK;
114 
115     listener = (GstMMDeviceEnumerator *) g_weak_ref_get (&enumerator_);
116     if (!listener)
117       return S_OK;
118 
119     hr = callbacks_.device_state_changed (listener, device_id, new_state,
120         user_data_);
121     gst_object_unref (listener);
122 
123     return hr;
124   }
125 
126   STDMETHODIMP
OnDeviceAdded(LPCWSTR device_id)127   OnDeviceAdded (LPCWSTR device_id)
128   {
129     GstMMDeviceEnumerator *listener;
130     HRESULT hr;
131 
132     if (!callbacks_.device_added)
133       return S_OK;
134 
135     listener = (GstMMDeviceEnumerator *) g_weak_ref_get (&enumerator_);
136     if (!listener)
137       return S_OK;
138 
139     hr = callbacks_.device_added (listener, device_id, user_data_);
140     gst_object_unref (listener);
141 
142     return hr;
143   }
144 
145   STDMETHODIMP
OnDeviceRemoved(LPCWSTR device_id)146   OnDeviceRemoved (LPCWSTR device_id)
147   {
148     GstMMDeviceEnumerator *listener;
149     HRESULT hr;
150 
151     if (!callbacks_.device_removed)
152       return S_OK;
153 
154     listener = (GstMMDeviceEnumerator *) g_weak_ref_get (&enumerator_);
155     if (!listener)
156       return S_OK;
157 
158     hr = callbacks_.device_removed (listener, device_id, user_data_);
159     gst_object_unref (listener);
160 
161     return hr;
162   }
163 
164   STDMETHODIMP
OnDefaultDeviceChanged(EDataFlow flow,ERole role,LPCWSTR default_device_id)165   OnDefaultDeviceChanged (EDataFlow flow, ERole role, LPCWSTR default_device_id)
166   {
167     GstMMDeviceEnumerator *listener;
168     HRESULT hr;
169 
170     if (!callbacks_.default_device_changed)
171       return S_OK;
172 
173     listener = (GstMMDeviceEnumerator *) g_weak_ref_get (&enumerator_);
174     if (!listener)
175       return S_OK;
176 
177     hr = callbacks_.default_device_changed (listener,
178         flow, role, default_device_id, user_data_);
179     gst_object_unref (listener);
180 
181     return hr;
182   }
183 
184   STDMETHODIMP
OnPropertyValueChanged(LPCWSTR device_id,const PROPERTYKEY key)185   OnPropertyValueChanged (LPCWSTR device_id, const PROPERTYKEY key)
186   {
187     GstMMDeviceEnumerator *listener;
188     HRESULT hr;
189 
190     if (!callbacks_.property_value_changed)
191       return S_OK;
192 
193     listener = (GstMMDeviceEnumerator *) g_weak_ref_get (&enumerator_);
194     if (!device_id)
195       return S_OK;
196 
197     hr = callbacks_.property_value_changed (listener,
198         device_id, key, user_data_);
199     gst_object_unref (listener);
200 
201     return hr;
202   }
203 
204 private:
GstIMMNotificationClient()205   GstIMMNotificationClient ()
206     : ref_count_ (1)
207   {
208     g_weak_ref_init (&enumerator_, nullptr);
209   }
210 
~GstIMMNotificationClient()211   virtual ~GstIMMNotificationClient ()
212   {
213     g_weak_ref_clear (&enumerator_);
214   }
215 
216 private:
217   ULONG ref_count_;
218   GstMMNotificationClientCallbacks callbacks_;
219   gpointer user_data_;
220   GWeakRef enumerator_;
221 };
222 /* *INDENT-ON* */
223 
224 struct _GstMMDeviceEnumerator
225 {
226   GstObject parent;
227 
228   IMMDeviceEnumerator *handle;
229   IMMNotificationClient *client;
230 
231   GMutex lock;
232   GCond cond;
233 
234   GThread *thread;
235   GMainContext *context;
236   GMainLoop *loop;
237 
238   gboolean running;
239 };
240 
241 static void gst_mm_device_enumerator_constructed (GObject * object);
242 static void gst_mm_device_enumerator_finalize (GObject * object);
243 
244 static gpointer
245 gst_mm_device_enumerator_thread_func (GstMMDeviceEnumerator * self);
246 
247 #define gst_mm_device_enumerator_parent_class parent_class
248 G_DEFINE_TYPE (GstMMDeviceEnumerator,
249     gst_mm_device_enumerator, GST_TYPE_OBJECT);
250 
251 static void
gst_mm_device_enumerator_class_init(GstMMDeviceEnumeratorClass * klass)252 gst_mm_device_enumerator_class_init (GstMMDeviceEnumeratorClass * klass)
253 {
254   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
255 
256   gobject_class->constructed = gst_mm_device_enumerator_constructed;
257   gobject_class->finalize = gst_mm_device_enumerator_finalize;
258 }
259 
260 static void
gst_mm_device_enumerator_init(GstMMDeviceEnumerator * self)261 gst_mm_device_enumerator_init (GstMMDeviceEnumerator * self)
262 {
263   g_mutex_init (&self->lock);
264   g_cond_init (&self->cond);
265   self->context = g_main_context_new ();
266   self->loop = g_main_loop_new (self->context, FALSE);
267 }
268 
269 static void
gst_mm_device_enumerator_constructed(GObject * object)270 gst_mm_device_enumerator_constructed (GObject * object)
271 {
272   GstMMDeviceEnumerator *self = GST_MM_DEVICE_ENUMERATOR (object);
273 
274   g_mutex_lock (&self->lock);
275   self->thread = g_thread_new ("GstMMDeviceEnumerator",
276       (GThreadFunc) gst_mm_device_enumerator_thread_func, self);
277   while (!g_main_loop_is_running (self->loop))
278     g_cond_wait (&self->cond, &self->lock);
279   g_mutex_unlock (&self->lock);
280 }
281 
282 static void
gst_mm_device_enumerator_finalize(GObject * object)283 gst_mm_device_enumerator_finalize (GObject * object)
284 {
285   GstMMDeviceEnumerator *self = GST_MM_DEVICE_ENUMERATOR (object);
286 
287   g_main_loop_quit (self->loop);
288   g_thread_join (self->thread);
289   g_main_loop_unref (self->loop);
290   g_main_context_unref (self->context);
291 
292   g_mutex_clear (&self->lock);
293   g_cond_clear (&self->cond);
294 
295   G_OBJECT_CLASS (parent_class)->finalize (object);
296 }
297 
298 static gboolean
loop_running_cb(GstMMDeviceEnumerator * self)299 loop_running_cb (GstMMDeviceEnumerator * self)
300 {
301   g_mutex_lock (&self->lock);
302   g_cond_signal (&self->cond);
303   g_mutex_unlock (&self->lock);
304 
305   return G_SOURCE_REMOVE;
306 }
307 
308 static gpointer
gst_mm_device_enumerator_thread_func(GstMMDeviceEnumerator * self)309 gst_mm_device_enumerator_thread_func (GstMMDeviceEnumerator * self)
310 {
311   GSource *idle_source;
312   IMMDeviceEnumerator *enumerator = nullptr;
313   HRESULT hr;
314 
315   CoInitializeEx (NULL, COINIT_MULTITHREADED);
316   g_main_context_push_thread_default (self->context);
317 
318   idle_source = g_idle_source_new ();
319   g_source_set_callback (idle_source,
320       (GSourceFunc) loop_running_cb, self, nullptr);
321   g_source_attach (idle_source, self->context);
322   g_source_unref (idle_source);
323 
324   hr = CoCreateInstance (__uuidof (MMDeviceEnumerator),
325       nullptr, CLSCTX_ALL, IID_PPV_ARGS (&enumerator));
326   if (FAILED (hr)) {
327     GST_ERROR_OBJECT (self, "Failed to create IMMDeviceEnumerator instance");
328     goto run_loop;
329   }
330 
331   self->handle = enumerator;
332 
333 run_loop:
334   GST_INFO_OBJECT (self, "Starting loop");
335   g_main_loop_run (self->loop);
336   GST_INFO_OBJECT (self, "Stopped loop");
337 
338   if (self->client && self->handle) {
339     self->handle->UnregisterEndpointNotificationCallback (self->client);
340 
341     self->client->Release ();
342   }
343 
344   if (self->handle)
345     self->handle->Release ();
346 
347   g_main_context_pop_thread_default (self->context);
348   CoUninitialize ();
349 
350   return nullptr;
351 }
352 
353 GstMMDeviceEnumerator *
gst_mm_device_enumerator_new(void)354 gst_mm_device_enumerator_new (void)
355 {
356   GstMMDeviceEnumerator *self;
357 
358   self = (GstMMDeviceEnumerator *) g_object_new (GST_TYPE_MM_DEVICE_ENUMERATOR,
359       nullptr);
360 
361   if (!self->handle) {
362     gst_object_unref (self);
363     return nullptr;
364   }
365 
366   gst_object_ref_sink (self);
367 
368   return self;
369 }
370 
371 IMMDeviceEnumerator *
gst_mm_device_enumerator_get_handle(GstMMDeviceEnumerator * enumerator)372 gst_mm_device_enumerator_get_handle (GstMMDeviceEnumerator * enumerator)
373 {
374   g_return_val_if_fail (GST_IS_MM_DEVICE_ENUMERATOR (enumerator), nullptr);
375 
376   return enumerator->handle;
377 }
378 
379 typedef struct
380 {
381   GstMMDeviceEnumerator *self;
382   GstMMNotificationClientCallbacks *callbacks;
383   gpointer user_data;
384 
385   gboolean handled;
386   GMutex lock;
387   GCond cond;
388 
389   gboolean ret;
390 } SetNotificationCallbackData;
391 
392 static gboolean
set_notification_callback(SetNotificationCallbackData * data)393 set_notification_callback (SetNotificationCallbackData * data)
394 {
395   GstMMDeviceEnumerator *self = data->self;
396   HRESULT hr;
397 
398   g_mutex_lock (&data->lock);
399   g_mutex_lock (&self->lock);
400 
401   data->ret = TRUE;
402 
403   if (self->client) {
404     self->handle->UnregisterEndpointNotificationCallback (self->client);
405     self->client->Release ();
406     self->client = nullptr;
407   }
408 
409   if (data->callbacks) {
410     IMMNotificationClient *client;
411 
412     hr = GstIMMNotificationClient::CreateInstance (self, data->callbacks,
413         data->user_data, &client);
414     if (FAILED (hr)) {
415       GST_ERROR_OBJECT (self,
416           "Failed to create IMMNotificationClient instance");
417       data->ret = FALSE;
418       goto out;
419     }
420 
421     hr = self->handle->RegisterEndpointNotificationCallback (client);
422     if (FAILED (hr)) {
423       GST_ERROR_OBJECT (self, "Failed to register callback");
424       client->Release ();
425       data->ret = FALSE;
426       goto out;
427     }
428 
429     self->client = client;
430   }
431 
432 out:
433   data->handled = TRUE;
434   g_cond_signal (&data->cond);
435   g_mutex_unlock (&self->lock);
436   g_mutex_unlock (&data->lock);
437 
438   return G_SOURCE_REMOVE;
439 }
440 
441 gboolean
gst_mm_device_enumerator_set_notification_callback(GstMMDeviceEnumerator * enumerator,GstMMNotificationClientCallbacks * callbacks,gpointer user_data)442 gst_mm_device_enumerator_set_notification_callback (GstMMDeviceEnumerator *
443     enumerator, GstMMNotificationClientCallbacks * callbacks,
444     gpointer user_data)
445 {
446   SetNotificationCallbackData data;
447   gboolean ret;
448 
449   g_return_val_if_fail (GST_IS_MM_DEVICE_ENUMERATOR (enumerator), FALSE);
450 
451   data.self = enumerator;
452   data.callbacks = callbacks;
453   data.user_data = user_data;
454   data.handled = FALSE;
455 
456   g_mutex_init (&data.lock);
457   g_cond_init (&data.cond);
458 
459   g_main_context_invoke (enumerator->context,
460       (GSourceFunc) set_notification_callback, &data);
461   g_mutex_lock (&data.lock);
462   while (!data.handled)
463     g_cond_wait (&data.cond, &data.lock);
464   g_mutex_unlock (&data.lock);
465 
466   ret = data.ret;
467 
468   g_mutex_clear (&data.lock);
469   g_cond_clear (&data.cond);
470 
471   return ret;
472 }
473