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