Skip to content

Commit 589ff8c

Browse files
committed
wasapideviceprovider: Add support for dynamic device add/remove
Adding IMMDeviceEnumerator::RegisterEndpointNotificationCallback in order to support device monitoring. On OnDeviceAdded(), OnDeviceRemoved(), and OnDefaultDeviceChanged() callback, wasapi device provider implementation will enumerate devices again and will notify newly added and removed device via GstDeviceProvider API. As a bonus point, this IMMDeviceEnumerator abstraction object will spawn a dedicated internal COM thread, so various COM thread related issues of WASAPI plugin can be resolved by this commit. Fixes: https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/issues/1649 Fixes: https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/issues/1110 Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2484>
1 parent 3c937c8 commit 589ff8c

12 files changed

+967
-75
lines changed

sys/wasapi/gstmmdeviceenumerator.cpp

+472
Large diffs are not rendered by default.

sys/wasapi/gstmmdeviceenumerator.h

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright (C) 2021 Seungha Yang <[email protected]>
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+
#ifndef __GST_MM_DEVICE_ENUMERATOR_H__
21+
#define __GST_MM_DEVICE_ENUMERATOR_H__
22+
23+
#include <gst/gst.h>
24+
#include <mmdeviceapi.h>
25+
26+
G_BEGIN_DECLS
27+
28+
#define GST_TYPE_MM_DEVICE_ENUMERATOR (gst_mm_device_enumerator_get_type ())
29+
G_DECLARE_FINAL_TYPE (GstMMDeviceEnumerator, gst_mm_device_enumerator,
30+
GST, MM_DEVICE_ENUMERATOR, GstObject);
31+
32+
typedef struct
33+
{
34+
HRESULT (*device_state_changed) (GstMMDeviceEnumerator * enumerator,
35+
LPCWSTR device_id,
36+
DWORD new_state,
37+
gpointer user_data);
38+
39+
HRESULT (*device_added) (GstMMDeviceEnumerator * enumerator,
40+
LPCWSTR device_id,
41+
gpointer user_data);
42+
43+
HRESULT (*device_removed) (GstMMDeviceEnumerator * provider,
44+
LPCWSTR device_id,
45+
gpointer user_data);
46+
47+
HRESULT (*default_device_changed) (GstMMDeviceEnumerator * provider,
48+
EDataFlow flow,
49+
ERole role,
50+
LPCWSTR default_device_id,
51+
gpointer user_data);
52+
53+
HRESULT (*property_value_changed) (GstMMDeviceEnumerator * provider,
54+
LPCWSTR device_id,
55+
const PROPERTYKEY key,
56+
gpointer user_data);
57+
} GstMMNotificationClientCallbacks;
58+
59+
GstMMDeviceEnumerator * gst_mm_device_enumerator_new (void);
60+
61+
IMMDeviceEnumerator * gst_mm_device_enumerator_get_handle (GstMMDeviceEnumerator * enumerator);
62+
63+
gboolean gst_mm_device_enumerator_set_notification_callback (GstMMDeviceEnumerator * enumerator,
64+
GstMMNotificationClientCallbacks * callbacks,
65+
gpointer user_data);
66+
67+
G_END_DECLS
68+
69+
#endif /* __GST_MM_DEVICE_ENUMERATOR_H__ */

sys/wasapi/gstwasapidevice.c

+201-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* GStreamer
22
* Copyright (C) 2018 Nirbheek Chauhan <[email protected]>
3+
* Copyright (C) 2021 Seungha Yang <[email protected]>
34
*
45
* This library is free software; you can redistribute it and/or
56
* modify it under the terms of the GNU Library General Public
@@ -23,11 +24,27 @@
2324

2425
#include "gstwasapidevice.h"
2526

27+
GST_DEBUG_CATEGORY_EXTERN (gst_wasapi_debug);
28+
#define GST_CAT_DEFAULT gst_wasapi_debug
29+
2630
G_DEFINE_TYPE (GstWasapiDeviceProvider, gst_wasapi_device_provider,
2731
GST_TYPE_DEVICE_PROVIDER);
2832

2933
static void gst_wasapi_device_provider_finalize (GObject * object);
3034
static GList *gst_wasapi_device_provider_probe (GstDeviceProvider * provider);
35+
static gboolean gst_wasapi_device_provider_start (GstDeviceProvider * provider);
36+
static void gst_wasapi_device_provider_stop (GstDeviceProvider * provider);
37+
38+
static HRESULT
39+
gst_wasapi_device_provider_device_added (GstMMDeviceEnumerator * enumerator,
40+
LPCWSTR device_id, gpointer user_data);
41+
static HRESULT
42+
gst_wasapi_device_provider_device_removed (GstMMDeviceEnumerator * enumerator,
43+
LPCWSTR device_id, gpointer user_data);
44+
static HRESULT
45+
gst_wasapi_device_provider_default_device_changed (GstMMDeviceEnumerator *
46+
enumerator, EDataFlow flow, ERole role, LPCWSTR device_id,
47+
gpointer user_data);
3148

3249
static void
3350
gst_wasapi_device_provider_class_init (GstWasapiDeviceProviderClass * klass)
@@ -38,6 +55,8 @@ gst_wasapi_device_provider_class_init (GstWasapiDeviceProviderClass * klass)
3855
gobject_class->finalize = gst_wasapi_device_provider_finalize;
3956

4057
dm_class->probe = gst_wasapi_device_provider_probe;
58+
dm_class->start = gst_wasapi_device_provider_start;
59+
dm_class->stop = gst_wasapi_device_provider_stop;
4160

4261
gst_device_provider_class_set_static_metadata (dm_class,
4362
"WASAPI (Windows Audio Session API) Device Provider",
@@ -46,15 +65,68 @@ gst_wasapi_device_provider_class_init (GstWasapiDeviceProviderClass * klass)
4665
}
4766

4867
static void
49-
gst_wasapi_device_provider_init (GstWasapiDeviceProvider * provider)
68+
gst_wasapi_device_provider_init (GstWasapiDeviceProvider * self)
5069
{
51-
CoInitializeEx (NULL, COINIT_MULTITHREADED);
70+
self->enumerator = gst_mm_device_enumerator_new ();
71+
}
72+
73+
static gboolean
74+
gst_wasapi_device_provider_start (GstDeviceProvider * provider)
75+
{
76+
GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (provider);
77+
GstMMNotificationClientCallbacks callbacks = { NULL, };
78+
GList *devices = NULL;
79+
GList *iter;
80+
81+
if (!self->enumerator) {
82+
GST_WARNING_OBJECT (self, "Enumerator wasn't configured");
83+
return FALSE;
84+
}
85+
86+
callbacks.device_added = gst_wasapi_device_provider_device_added;
87+
callbacks.device_removed = gst_wasapi_device_provider_device_removed;
88+
callbacks.default_device_changed =
89+
gst_wasapi_device_provider_default_device_changed;
90+
91+
if (!gst_mm_device_enumerator_set_notification_callback (self->enumerator,
92+
&callbacks, self)) {
93+
GST_WARNING_OBJECT (self, "Failed to set callbacks");
94+
return FALSE;
95+
}
96+
97+
/* baseclass will not call probe() once it's started, but we can get
98+
* notification only add/remove or change case. To this manually */
99+
devices = gst_wasapi_device_provider_probe (provider);
100+
if (devices) {
101+
for (iter = devices; iter; iter = g_list_next (iter)) {
102+
gst_device_provider_device_add (provider, GST_DEVICE (iter->data));
103+
}
104+
105+
g_list_free (devices);
106+
}
107+
108+
return TRUE;
109+
}
110+
111+
static void
112+
gst_wasapi_device_provider_stop (GstDeviceProvider * provider)
113+
{
114+
GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (provider);
115+
116+
if (self->enumerator) {
117+
gst_mm_device_enumerator_set_notification_callback (self->enumerator,
118+
NULL, NULL);
119+
}
52120
}
53121

54122
static void
55123
gst_wasapi_device_provider_finalize (GObject * object)
56124
{
57-
CoUninitialize ();
125+
GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (object);
126+
127+
gst_clear_object (&self->enumerator);
128+
129+
G_OBJECT_CLASS (gst_wasapi_device_provider_parent_class)->finalize (object);
58130
}
59131

60132
static GList *
@@ -63,12 +135,137 @@ gst_wasapi_device_provider_probe (GstDeviceProvider * provider)
63135
GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (provider);
64136
GList *devices = NULL;
65137

66-
if (!gst_wasapi_util_get_devices (GST_OBJECT (self), TRUE, &devices))
138+
if (!gst_wasapi_util_get_devices (self->enumerator, TRUE, &devices))
67139
GST_ERROR_OBJECT (self, "Failed to enumerate devices");
68140

69141
return devices;
70142
}
71143

144+
static gboolean
145+
gst_wasapi_device_is_in_list (GList * list, GstDevice * device)
146+
{
147+
GList *iter;
148+
GstStructure *s;
149+
const gchar *device_id;
150+
gboolean found = FALSE;
151+
152+
s = gst_device_get_properties (device);
153+
g_assert (s);
154+
155+
device_id = gst_structure_get_string (s, "device.strid");
156+
g_assert (device_id);
157+
158+
for (iter = list; iter; iter = g_list_next (iter)) {
159+
GstStructure *other_s;
160+
const gchar *other_id;
161+
162+
other_s = gst_device_get_properties (GST_DEVICE (iter->data));
163+
g_assert (other_s);
164+
165+
other_id = gst_structure_get_string (other_s, "device.strid");
166+
g_assert (other_id);
167+
168+
if (g_ascii_strcasecmp (device_id, other_id) == 0) {
169+
found = TRUE;
170+
}
171+
172+
gst_structure_free (other_s);
173+
if (found)
174+
break;
175+
}
176+
177+
gst_structure_free (s);
178+
179+
return found;
180+
}
181+
182+
static void
183+
gst_wasapi_device_provider_update_devices (GstWasapiDeviceProvider * self)
184+
{
185+
GstDeviceProvider *provider = GST_DEVICE_PROVIDER_CAST (self);
186+
GList *prev_devices = NULL;
187+
GList *new_devices = NULL;
188+
GList *to_add = NULL;
189+
GList *to_remove = NULL;
190+
GList *iter;
191+
192+
GST_OBJECT_LOCK (self);
193+
prev_devices = g_list_copy_deep (provider->devices,
194+
(GCopyFunc) gst_object_ref, NULL);
195+
GST_OBJECT_UNLOCK (self);
196+
197+
new_devices = gst_wasapi_device_provider_probe (provider);
198+
199+
/* Ownership of GstDevice for gst_device_provider_device_add()
200+
* and gst_device_provider_device_remove() is a bit complicated.
201+
* Remove floating reference here for things to be clear */
202+
for (iter = new_devices; iter; iter = g_list_next (iter))
203+
gst_object_ref_sink (iter->data);
204+
205+
/* Check newly added devices */
206+
for (iter = new_devices; iter; iter = g_list_next (iter)) {
207+
if (!gst_wasapi_device_is_in_list (prev_devices, GST_DEVICE (iter->data))) {
208+
to_add = g_list_prepend (to_add, gst_object_ref (iter->data));
209+
}
210+
}
211+
212+
/* Check removed device */
213+
for (iter = prev_devices; iter; iter = g_list_next (iter)) {
214+
if (!gst_wasapi_device_is_in_list (new_devices, GST_DEVICE (iter->data))) {
215+
to_remove = g_list_prepend (to_remove, gst_object_ref (iter->data));
216+
}
217+
}
218+
219+
for (iter = to_remove; iter; iter = g_list_next (iter))
220+
gst_device_provider_device_remove (provider, GST_DEVICE (iter->data));
221+
222+
for (iter = to_add; iter; iter = g_list_next (iter))
223+
gst_device_provider_device_add (provider, GST_DEVICE (iter->data));
224+
225+
if (prev_devices)
226+
g_list_free_full (prev_devices, (GDestroyNotify) gst_object_unref);
227+
228+
if (to_add)
229+
g_list_free_full (to_add, (GDestroyNotify) gst_object_unref);
230+
231+
if (to_remove)
232+
g_list_free_full (to_remove, (GDestroyNotify) gst_object_unref);
233+
}
234+
235+
static HRESULT
236+
gst_wasapi_device_provider_device_added (GstMMDeviceEnumerator * enumerator,
237+
LPCWSTR device_id, gpointer user_data)
238+
{
239+
GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (user_data);
240+
241+
gst_wasapi_device_provider_update_devices (self);
242+
243+
return S_OK;
244+
}
245+
246+
static HRESULT
247+
gst_wasapi_device_provider_device_removed (GstMMDeviceEnumerator * enumerator,
248+
LPCWSTR device_id, gpointer user_data)
249+
{
250+
GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (user_data);
251+
252+
gst_wasapi_device_provider_update_devices (self);
253+
254+
return S_OK;
255+
}
256+
257+
static HRESULT
258+
gst_wasapi_device_provider_default_device_changed (GstMMDeviceEnumerator *
259+
enumerator, EDataFlow flow, ERole role, LPCWSTR device_id,
260+
gpointer user_data)
261+
{
262+
GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (user_data);
263+
264+
gst_wasapi_device_provider_update_devices (self);
265+
266+
return S_OK;
267+
}
268+
72269
/* GstWasapiDevice begins */
73270

74271
enum

sys/wasapi/gstwasapidevice.h

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ typedef struct _GstWasapiDeviceProviderClass GstWasapiDeviceProviderClass;
3838
struct _GstWasapiDeviceProvider
3939
{
4040
GstDeviceProvider parent;
41+
42+
GstMMDeviceEnumerator *enumerator;
4143
};
4244

4345
struct _GstWasapiDeviceProviderClass

sys/wasapi/gstwasapisink.c

+4-8
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ gst_wasapi_sink_init (GstWasapiSink * self)
180180
self->cancellable = CreateEvent (NULL, TRUE, FALSE, NULL);
181181
self->client_needs_restart = FALSE;
182182

183-
CoInitializeEx (NULL, COINIT_MULTITHREADED);
183+
self->enumerator = gst_mm_device_enumerator_new ();
184184
}
185185

186186
static void
@@ -208,6 +208,8 @@ gst_wasapi_sink_dispose (GObject * object)
208208
self->render_client = NULL;
209209
}
210210

211+
gst_clear_object (&self->enumerator);
212+
211213
G_OBJECT_CLASS (gst_wasapi_sink_parent_class)->dispose (object);
212214
}
213215

@@ -219,8 +221,6 @@ gst_wasapi_sink_finalize (GObject * object)
219221
CoTaskMemFree (self->mix_format);
220222
self->mix_format = NULL;
221223

222-
CoUninitialize ();
223-
224224
if (self->cached_caps != NULL) {
225225
gst_caps_unref (self->cached_caps);
226226
self->cached_caps = NULL;
@@ -412,7 +412,7 @@ gst_wasapi_sink_open (GstAudioSink * asink)
412412
* even if the old device was unplugged. We need to handle this somehow.
413413
* For example, perhaps we should automatically switch to the new device if
414414
* the default device is changed and a device isn't explicitly selected. */
415-
if (!gst_wasapi_util_get_device (GST_ELEMENT (self), eRender,
415+
if (!gst_wasapi_util_get_device (self->enumerator, eRender,
416416
self->role, self->device_strid, &device)
417417
|| !gst_wasapi_util_get_audio_client (GST_ELEMENT (self),
418418
device, &client)) {
@@ -485,8 +485,6 @@ gst_wasapi_sink_prepare (GstAudioSink * asink, GstAudioRingBufferSpec * spec)
485485
guint bpf, rate, devicep_frames;
486486
HRESULT hr;
487487

488-
CoInitializeEx (NULL, COINIT_MULTITHREADED);
489-
490488
if (!self->client) {
491489
GST_DEBUG_OBJECT (self, "no IAudioClient, creating a new one");
492490
if (!gst_wasapi_util_get_audio_client (GST_ELEMENT (self),
@@ -608,8 +606,6 @@ gst_wasapi_sink_unprepare (GstAudioSink * asink)
608606
self->render_client = NULL;
609607
}
610608

611-
CoUninitialize ();
612-
613609
return TRUE;
614610
}
615611

sys/wasapi/gstwasapisink.h

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ struct _GstWasapiSink
4040
{
4141
GstAudioSink parent;
4242

43+
GstMMDeviceEnumerator *enumerator;
44+
4345
IMMDevice *device;
4446
IAudioClient *client;
4547
IAudioRenderClient *render_client;

0 commit comments

Comments
 (0)