1 /*
2 * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
3 * Copyright (C) 2013 Collabora Ltd.
4 * Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>
5 * Copyright (C) 2018 Centricular Ltd.
6 * Author: Nirbheek Chauhan <nirbheek@centricular.com>
7 * Copyright (C) 2020 Seungha Yang <seungha@centricular.com>
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public
20 * License along with this library; if not, write to the
21 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 */
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28
29 #include "AsyncOperations.h"
30 #include "gstwasapi2client.h"
31 #include "gstwasapi2util.h"
32 #include <initguid.h>
33 #include <windows.foundation.h>
34 #include <windows.ui.core.h>
35 #include <wrl.h>
36 #include <wrl/wrappers/corewrappers.h>
37 #include <audioclient.h>
38 #include <mmdeviceapi.h>
39 #include <string.h>
40 #include <string>
41 #include <locale>
42 #include <codecvt>
43
44 /* *INDENT-OFF* */
45 using namespace ABI::Windows::ApplicationModel::Core;
46 using namespace ABI::Windows::Foundation;
47 using namespace ABI::Windows::Foundation::Collections;
48 using namespace ABI::Windows::UI::Core;
49 using namespace ABI::Windows::Media::Devices;
50 using namespace ABI::Windows::Devices::Enumeration;
51
52 using namespace Microsoft::WRL;
53 using namespace Microsoft::WRL::Wrappers;
54
55 G_BEGIN_DECLS
56
57 GST_DEBUG_CATEGORY_EXTERN (gst_wasapi2_client_debug);
58 #define GST_CAT_DEFAULT gst_wasapi2_client_debug
59
60 G_END_DECLS
61 /* *INDENT-ON* */
62
63 static void
64 gst_wasapi2_client_on_device_activated (GstWasapi2Client * client,
65 IAudioClient * audio_client);
66
67 /* *INDENT-OFF* */
68 class GstWasapiDeviceActivator
69 : public RuntimeClass<RuntimeClassFlags<ClassicCom>, FtmBase,
70 IActivateAudioInterfaceCompletionHandler>
71 {
72 public:
GstWasapiDeviceActivator()73 GstWasapiDeviceActivator ()
74 {
75 g_weak_ref_init (&listener_, nullptr);
76 }
77
~GstWasapiDeviceActivator()78 ~GstWasapiDeviceActivator ()
79 {
80 g_weak_ref_set (&listener_, nullptr);
81 }
82
83 HRESULT
RuntimeClassInitialize(GstWasapi2Client * listener,gpointer dispatcher)84 RuntimeClassInitialize (GstWasapi2Client * listener, gpointer dispatcher)
85 {
86 if (!listener)
87 return E_INVALIDARG;
88
89 g_weak_ref_set (&listener_, listener);
90
91 if (dispatcher) {
92 ComPtr<IInspectable> inspectable =
93 reinterpret_cast<IInspectable*> (dispatcher);
94 HRESULT hr;
95
96 hr = inspectable.As (&dispatcher_);
97 if (gst_wasapi2_result (hr))
98 GST_INFO("Main UI dispatcher is available");
99 }
100
101 return S_OK;
102 }
103
STDMETHOD(ActivateCompleted)104 STDMETHOD(ActivateCompleted)
105 (IActivateAudioInterfaceAsyncOperation *async_op)
106 {
107 ComPtr<IAudioClient> audio_client;
108 HRESULT hr = S_OK;
109 HRESULT hr_async_op = S_OK;
110 ComPtr<IUnknown> audio_interface;
111 GstWasapi2Client *client;
112
113 client = (GstWasapi2Client *) g_weak_ref_get (&listener_);
114
115 if (!client) {
116 this->Release ();
117 GST_WARNING ("No listener was configured");
118 return S_OK;
119 }
120
121 GST_INFO_OBJECT (client, "AsyncOperation done");
122
123 hr = async_op->GetActivateResult(&hr_async_op, &audio_interface);
124
125 if (!gst_wasapi2_result (hr)) {
126 GST_WARNING_OBJECT (client, "Failed to get activate result, hr: 0x%x", hr);
127 goto done;
128 }
129
130 if (!gst_wasapi2_result (hr_async_op)) {
131 GST_WARNING_OBJECT (client, "Failed to activate device");
132 goto done;
133 }
134
135 hr = audio_interface.As (&audio_client);
136 if (!gst_wasapi2_result (hr)) {
137 GST_ERROR_OBJECT (client, "Failed to get IAudioClient3 interface");
138 goto done;
139 }
140
141 done:
142 /* Should call this method anyway, listener will wait this event */
143 gst_wasapi2_client_on_device_activated (client, audio_client.Get());
144 gst_object_unref (client);
145 /* return S_OK anyway, but listener can know it's succeeded or not
146 * by passed IAudioClient handle via gst_wasapi2_client_on_device_activated
147 */
148
149 this->Release ();
150
151 return S_OK;
152 }
153
154 HRESULT
ActivateDeviceAsync(const std::wstring & device_id)155 ActivateDeviceAsync(const std::wstring &device_id)
156 {
157 ComPtr<IAsyncAction> async_action;
158 bool run_async = false;
159 HRESULT hr;
160
161 auto work_item = Callback<Implements<RuntimeClassFlags<ClassicCom>,
162 IDispatchedHandler, FtmBase>>([this, device_id]{
163 ComPtr<IActivateAudioInterfaceAsyncOperation> async_op;
164 HRESULT async_hr = S_OK;
165
166 async_hr = ActivateAudioInterfaceAsync (device_id.c_str (),
167 __uuidof(IAudioClient), nullptr, this, &async_op);
168
169 /* for debugging */
170 gst_wasapi2_result (async_hr);
171
172 return async_hr;
173 });
174
175 if (dispatcher_) {
176 boolean can_now;
177 hr = dispatcher_->get_HasThreadAccess (&can_now);
178
179 if (!gst_wasapi2_result (hr))
180 return hr;
181
182 if (!can_now)
183 run_async = true;
184 }
185
186 if (run_async && dispatcher_) {
187 hr = dispatcher_->RunAsync (CoreDispatcherPriority_Normal,
188 work_item.Get (), &async_action);
189 } else {
190 hr = work_item->Invoke ();
191 }
192
193 /* We should hold activator object until activation callback has executed,
194 * because OS doesn't hold reference of this callback COM object.
195 * otherwise access violation would happen
196 * See https://docs.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-activateaudiointerfaceasync
197 *
198 * This reference count will be decreased by self later on callback,
199 * which will be called from device worker thread.
200 */
201 if (gst_wasapi2_result (hr))
202 this->AddRef ();
203
204 return hr;
205 }
206
207 private:
208 GWeakRef listener_;
209 ComPtr<ICoreDispatcher> dispatcher_;
210 };
211 /* *INDENT-ON* */
212
213 typedef enum
214 {
215 GST_WASAPI2_CLIENT_ACTIVATE_FAILED = -1,
216 GST_WASAPI2_CLIENT_ACTIVATE_INIT = 0,
217 GST_WASAPI2_CLIENT_ACTIVATE_WAIT,
218 GST_WASAPI2_CLIENT_ACTIVATE_DONE,
219 } GstWasapi2ClientActivateState;
220
221 enum
222 {
223 PROP_0,
224 PROP_DEVICE,
225 PROP_DEVICE_NAME,
226 PROP_DEVICE_INDEX,
227 PROP_DEVICE_CLASS,
228 PROP_DISPATCHER,
229 PROP_CAN_AUTO_ROUTING,
230 };
231
232 #define DEFAULT_DEVICE_INDEX -1
233 #define DEFAULT_DEVICE_CLASS GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE
234
235 struct _GstWasapi2Client
236 {
237 GstObject parent;
238
239 GstWasapi2ClientDeviceClass device_class;
240 gchar *device_id;
241 gchar *device_name;
242 gint device_index;
243 gpointer dispatcher;
244 gboolean can_auto_routing;
245
246 IAudioClient *audio_client;
247 GstWasapiDeviceActivator *activator;
248
249 GstCaps *supported_caps;
250
251 GThread *thread;
252 GMutex lock;
253 GCond cond;
254 GMainContext *context;
255 GMainLoop *loop;
256
257 /* To wait ActivateCompleted event */
258 GMutex init_lock;
259 GCond init_cond;
260 GstWasapi2ClientActivateState activate_state;
261 };
262
263 GType
gst_wasapi2_client_device_class_get_type(void)264 gst_wasapi2_client_device_class_get_type (void)
265 {
266 static GType class_type = 0;
267 static const GEnumValue types[] = {
268 {GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE, "Capture", "capture"},
269 {GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER, "Render", "render"},
270 {GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE, "Loopback-Capture",
271 "loopback-capture"},
272 {0, nullptr, nullptr}
273 };
274
275 if (g_once_init_enter (&class_type)) {
276 GType gtype = g_enum_register_static ("GstWasapi2ClientDeviceClass", types);
277 g_once_init_leave (&class_type, gtype);
278 }
279
280 return class_type;
281 }
282
283 static void gst_wasapi2_client_constructed (GObject * object);
284 static void gst_wasapi2_client_finalize (GObject * object);
285 static void gst_wasapi2_client_get_property (GObject * object, guint prop_id,
286 GValue * value, GParamSpec * pspec);
287 static void gst_wasapi2_client_set_property (GObject * object, guint prop_id,
288 const GValue * value, GParamSpec * pspec);
289
290 static gpointer gst_wasapi2_client_thread_func (GstWasapi2Client * self);
291 static gboolean
292 gst_wasapi2_client_main_loop_running_cb (GstWasapi2Client * self);
293
294 #define gst_wasapi2_client_parent_class parent_class
295 G_DEFINE_TYPE (GstWasapi2Client, gst_wasapi2_client, GST_TYPE_OBJECT);
296
297 static void
gst_wasapi2_client_class_init(GstWasapi2ClientClass * klass)298 gst_wasapi2_client_class_init (GstWasapi2ClientClass * klass)
299 {
300 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
301 GParamFlags param_flags =
302 (GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
303 G_PARAM_STATIC_STRINGS);
304
305 gobject_class->constructed = gst_wasapi2_client_constructed;
306 gobject_class->finalize = gst_wasapi2_client_finalize;
307 gobject_class->get_property = gst_wasapi2_client_get_property;
308 gobject_class->set_property = gst_wasapi2_client_set_property;
309
310 g_object_class_install_property (gobject_class, PROP_DEVICE,
311 g_param_spec_string ("device", "Device",
312 "WASAPI playback device as a GUID string", nullptr, param_flags));
313 g_object_class_install_property (gobject_class, PROP_DEVICE_NAME,
314 g_param_spec_string ("device-name", "Device Name",
315 "The human-readable device name", nullptr, param_flags));
316 g_object_class_install_property (gobject_class, PROP_DEVICE_INDEX,
317 g_param_spec_int ("device-index", "Device Index",
318 "The zero-based device index", -1, G_MAXINT, DEFAULT_DEVICE_INDEX,
319 param_flags));
320 g_object_class_install_property (gobject_class, PROP_DEVICE_CLASS,
321 g_param_spec_enum ("device-class", "Device Class",
322 "Device class", GST_TYPE_WASAPI2_CLIENT_DEVICE_CLASS,
323 DEFAULT_DEVICE_CLASS, param_flags));
324 g_object_class_install_property (gobject_class, PROP_DISPATCHER,
325 g_param_spec_pointer ("dispatcher", "Dispatcher",
326 "ICoreDispatcher COM object to use", param_flags));
327 g_object_class_install_property (gobject_class, PROP_CAN_AUTO_ROUTING,
328 g_param_spec_boolean ("auto-routing", "Auto Routing",
329 "Whether client can support automatic stream routing", FALSE,
330 (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
331 }
332
333 static void
gst_wasapi2_client_init(GstWasapi2Client * self)334 gst_wasapi2_client_init (GstWasapi2Client * self)
335 {
336 self->device_index = DEFAULT_DEVICE_INDEX;
337 self->device_class = DEFAULT_DEVICE_CLASS;
338 self->can_auto_routing = FALSE;
339
340 g_mutex_init (&self->lock);
341 g_cond_init (&self->cond);
342
343 g_mutex_init (&self->init_lock);
344 g_cond_init (&self->init_cond);
345 self->activate_state = GST_WASAPI2_CLIENT_ACTIVATE_INIT;
346
347 self->context = g_main_context_new ();
348 self->loop = g_main_loop_new (self->context, FALSE);
349 }
350
351 static void
gst_wasapi2_client_constructed(GObject * object)352 gst_wasapi2_client_constructed (GObject * object)
353 {
354 GstWasapi2Client *self = GST_WASAPI2_CLIENT (object);
355 /* *INDENT-OFF* */
356 ComPtr<GstWasapiDeviceActivator> activator;
357 /* *INDENT-ON* */
358
359 /* Create a new thread to ensure that COM thread can be MTA thread.
360 * We cannot ensure whether CoInitializeEx() was called outside of here for
361 * this thread or not. If it was called with non-COINIT_MULTITHREADED option,
362 * we cannot update it */
363 g_mutex_lock (&self->lock);
364 self->thread = g_thread_new ("GstWasapi2ClientWinRT",
365 (GThreadFunc) gst_wasapi2_client_thread_func, self);
366 while (!self->loop || !g_main_loop_is_running (self->loop))
367 g_cond_wait (&self->cond, &self->lock);
368 g_mutex_unlock (&self->lock);
369
370 G_OBJECT_CLASS (parent_class)->constructed (object);
371 }
372
373 static void
gst_wasapi2_client_finalize(GObject * object)374 gst_wasapi2_client_finalize (GObject * object)
375 {
376 GstWasapi2Client *self = GST_WASAPI2_CLIENT (object);
377
378 if (self->loop) {
379 g_main_loop_quit (self->loop);
380 g_thread_join (self->thread);
381 g_main_context_unref (self->context);
382 g_main_loop_unref (self->loop);
383
384 self->thread = nullptr;
385 self->context = nullptr;
386 self->loop = nullptr;
387 }
388
389 gst_clear_caps (&self->supported_caps);
390
391 g_free (self->device_id);
392 g_free (self->device_name);
393
394 g_mutex_clear (&self->lock);
395 g_cond_clear (&self->cond);
396
397 g_mutex_clear (&self->init_lock);
398 g_cond_clear (&self->init_cond);
399
400 G_OBJECT_CLASS (parent_class)->finalize (object);
401 }
402
403 static void
gst_wasapi2_client_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)404 gst_wasapi2_client_get_property (GObject * object, guint prop_id,
405 GValue * value, GParamSpec * pspec)
406 {
407 GstWasapi2Client *self = GST_WASAPI2_CLIENT (object);
408
409 switch (prop_id) {
410 case PROP_DEVICE:
411 g_value_set_string (value, self->device_id);
412 break;
413 case PROP_DEVICE_NAME:
414 g_value_set_string (value, self->device_name);
415 break;
416 case PROP_DEVICE_INDEX:
417 g_value_set_int (value, self->device_index);
418 break;
419 case PROP_DEVICE_CLASS:
420 g_value_set_enum (value, self->device_class);
421 break;
422 case PROP_DISPATCHER:
423 g_value_set_pointer (value, self->dispatcher);
424 break;
425 case PROP_CAN_AUTO_ROUTING:
426 g_value_set_boolean (value, self->can_auto_routing);
427 break;
428 default:
429 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
430 break;
431 }
432 }
433
434 static void
gst_wasapi2_client_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)435 gst_wasapi2_client_set_property (GObject * object, guint prop_id,
436 const GValue * value, GParamSpec * pspec)
437 {
438 GstWasapi2Client *self = GST_WASAPI2_CLIENT (object);
439
440 switch (prop_id) {
441 case PROP_DEVICE:
442 g_free (self->device_id);
443 self->device_id = g_value_dup_string (value);
444 break;
445 case PROP_DEVICE_NAME:
446 g_free (self->device_name);
447 self->device_name = g_value_dup_string (value);
448 break;
449 case PROP_DEVICE_INDEX:
450 self->device_index = g_value_get_int (value);
451 break;
452 case PROP_DEVICE_CLASS:
453 self->device_class =
454 (GstWasapi2ClientDeviceClass) g_value_get_enum (value);
455 break;
456 case PROP_DISPATCHER:
457 self->dispatcher = g_value_get_pointer (value);
458 break;
459 default:
460 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
461 break;
462 }
463 }
464
465 static gboolean
gst_wasapi2_client_main_loop_running_cb(GstWasapi2Client * self)466 gst_wasapi2_client_main_loop_running_cb (GstWasapi2Client * self)
467 {
468 GST_DEBUG_OBJECT (self, "Main loop running now");
469
470 g_mutex_lock (&self->lock);
471 g_cond_signal (&self->cond);
472 g_mutex_unlock (&self->lock);
473
474 return G_SOURCE_REMOVE;
475 }
476
477 static void
gst_wasapi2_client_on_device_activated(GstWasapi2Client * self,IAudioClient * audio_client)478 gst_wasapi2_client_on_device_activated (GstWasapi2Client * self,
479 IAudioClient * audio_client)
480 {
481 GST_INFO_OBJECT (self, "Device activated");
482
483 g_mutex_lock (&self->init_lock);
484 if (audio_client) {
485 audio_client->AddRef ();
486 self->audio_client = audio_client;
487 self->activate_state = GST_WASAPI2_CLIENT_ACTIVATE_DONE;
488 } else {
489 GST_WARNING_OBJECT (self, "IAudioClient is unavailable");
490 self->activate_state = GST_WASAPI2_CLIENT_ACTIVATE_FAILED;
491 }
492 g_cond_broadcast (&self->init_cond);
493 g_mutex_unlock (&self->init_lock);
494 }
495
496 /* *INDENT-OFF* */
497 static std::string
convert_wstring_to_string(const std::wstring & wstr)498 convert_wstring_to_string (const std::wstring &wstr)
499 {
500 std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
501
502 return converter.to_bytes (wstr.c_str());
503 }
504
505 static std::string
convert_hstring_to_string(HString * hstr)506 convert_hstring_to_string (HString * hstr)
507 {
508 const wchar_t *raw_hstr;
509
510 if (!hstr)
511 return std::string();
512
513 raw_hstr = hstr->GetRawBuffer (nullptr);
514 if (!raw_hstr)
515 return std::string();
516
517 return convert_wstring_to_string (std::wstring (raw_hstr));
518 }
519
520 static std::wstring
gst_wasapi2_client_get_default_device_id(GstWasapi2Client * self)521 gst_wasapi2_client_get_default_device_id (GstWasapi2Client * self)
522 {
523 HRESULT hr;
524 PWSTR default_device_id_wstr = nullptr;
525
526 if (self->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE)
527 hr = StringFromIID (DEVINTERFACE_AUDIO_CAPTURE, &default_device_id_wstr);
528 else
529 hr = StringFromIID (DEVINTERFACE_AUDIO_RENDER, &default_device_id_wstr);
530
531 if (!gst_wasapi2_result (hr))
532 return std::wstring();
533
534 std::wstring ret = std::wstring (default_device_id_wstr);
535 CoTaskMemFree (default_device_id_wstr);
536
537 return ret;
538 }
539 /* *INDENT-ON* */
540
541 static gboolean
gst_wasapi2_client_activate_async(GstWasapi2Client * self,GstWasapiDeviceActivator * activator)542 gst_wasapi2_client_activate_async (GstWasapi2Client * self,
543 GstWasapiDeviceActivator * activator)
544 {
545 /* *INDENT-OFF* */
546 ComPtr<IDeviceInformationStatics> device_info_static;
547 ComPtr<IAsyncOperation<DeviceInformationCollection*>> async_op;
548 ComPtr<IVectorView<DeviceInformation*>> device_list;
549 HStringReference hstr_device_info =
550 HStringReference(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation);
551 /* *INDENT-ON* */
552 HRESULT hr;
553 DeviceClass device_class;
554 unsigned int count = 0;
555 gint device_index = 0;
556 std::wstring default_device_id_wstring;
557 std::string default_device_id;
558 std::wstring target_device_id_wstring;
559 std::string target_device_id;
560 std::string target_device_name;
561 gboolean use_default_device = FALSE;
562
563 GST_INFO_OBJECT (self,
564 "requested device info, device-class: %s, device: %s, device-index: %d",
565 self->device_class ==
566 GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE ? "capture" : "render",
567 GST_STR_NULL (self->device_id), self->device_index);
568
569 if (self->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE) {
570 device_class = DeviceClass::DeviceClass_AudioCapture;
571 } else {
572 device_class = DeviceClass::DeviceClass_AudioRender;
573 }
574
575 default_device_id_wstring = gst_wasapi2_client_get_default_device_id (self);
576 if (default_device_id_wstring.empty ()) {
577 GST_WARNING_OBJECT (self, "Couldn't get default device id");
578 goto failed;
579 }
580
581 default_device_id = convert_wstring_to_string (default_device_id_wstring);
582 GST_DEBUG_OBJECT (self, "Default device id: %s", default_device_id.c_str ());
583
584 /* When
585 * 1) default device was requested or
586 * 2) no explicitly requested device or
587 * 3) requested device string id is null but device index is zero
588 * will use default device
589 *
590 * Note that default device is much preferred
591 * See https://docs.microsoft.com/en-us/windows/win32/coreaudio/automatic-stream-routing
592 */
593
594 /* DEVINTERFACE_AUDIO_CAPTURE and DEVINTERFACE_AUDIO_RENDER are available
595 * as of Windows 10 */
596 if (gst_wasapi2_can_automatic_stream_routing ()) {
597 if (self->device_id &&
598 g_ascii_strcasecmp (self->device_id, default_device_id.c_str ()) == 0) {
599 GST_DEBUG_OBJECT (self, "Default device was requested");
600 use_default_device = TRUE;
601 } else if (self->device_index < 0 && !self->device_id) {
602 GST_DEBUG_OBJECT (self,
603 "No device was explicitly requested, use default device");
604 use_default_device = TRUE;
605 } else if (!self->device_id && self->device_index == 0) {
606 GST_DEBUG_OBJECT (self, "device-index == zero means default device");
607 use_default_device = TRUE;
608 }
609 }
610
611 if (use_default_device) {
612 target_device_id_wstring = default_device_id_wstring;
613 target_device_id = default_device_id;
614 if (self->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE)
615 target_device_name = "Default Audio Capture Device";
616 else
617 target_device_name = "Default Audio Render Device";
618 goto activate;
619 }
620
621 hr = GetActivationFactory (hstr_device_info.Get (), &device_info_static);
622 if (!gst_wasapi2_result (hr))
623 goto failed;
624
625 hr = device_info_static->FindAllAsyncDeviceClass (device_class, &async_op);
626 device_info_static.Reset ();
627 if (!gst_wasapi2_result (hr))
628 goto failed;
629
630 /* *INDENT-OFF* */
631 hr = SyncWait<DeviceInformationCollection*>(async_op.Get ());
632 /* *INDENT-ON* */
633 if (!gst_wasapi2_result (hr))
634 goto failed;
635
636 hr = async_op->GetResults (&device_list);
637 async_op.Reset ();
638 if (!gst_wasapi2_result (hr))
639 goto failed;
640
641 hr = device_list->get_Size (&count);
642 if (!gst_wasapi2_result (hr))
643 goto failed;
644
645 if (count == 0) {
646 GST_WARNING_OBJECT (self, "No available device");
647 goto failed;
648 }
649
650 /* device_index 0 will be assigned for default device
651 * so the number of available device is count + 1 (for default device) */
652 if (self->device_index >= 0 && self->device_index > (gint) count) {
653 GST_WARNING_OBJECT (self, "Device index %d is unavailable",
654 self->device_index);
655 goto failed;
656 }
657
658 GST_DEBUG_OBJECT (self, "Available device count: %d", count);
659
660 if (gst_wasapi2_can_automatic_stream_routing ()) {
661 /* zero is for default device */
662 device_index = 1;
663 } else {
664 device_index = 0;
665 }
666
667 for (unsigned int i = 0; i < count; i++) {
668 /* *INDENT-OFF* */
669 ComPtr<IDeviceInformation> device_info;
670 /* *INDENT-ON* */
671 HString id;
672 HString name;
673 boolean b_value;
674 std::string cur_device_id;
675 std::string cur_device_name;
676
677 hr = device_list->GetAt (i, &device_info);
678 if (!gst_wasapi2_result (hr))
679 continue;
680
681 hr = device_info->get_IsEnabled (&b_value);
682 if (!gst_wasapi2_result (hr))
683 continue;
684
685 /* select only enabled device */
686 if (!b_value) {
687 GST_DEBUG_OBJECT (self, "Device index %d is disabled", i);
688 continue;
689 }
690
691 /* To ensure device id and device name are available,
692 * will query this later again once target device is determined */
693 hr = device_info->get_Id (id.GetAddressOf ());
694 if (!gst_wasapi2_result (hr))
695 continue;
696
697 if (!id.IsValid ()) {
698 GST_WARNING_OBJECT (self, "Device index %d has invalid id", i);
699 continue;
700 }
701
702 hr = device_info->get_Name (name.GetAddressOf ());
703 if (!gst_wasapi2_result (hr))
704 continue;
705
706 if (!name.IsValid ()) {
707 GST_WARNING_OBJECT (self, "Device index %d has invalid name", i);
708 continue;
709 }
710
711 cur_device_id = convert_hstring_to_string (&id);
712 if (cur_device_id.empty ()) {
713 GST_WARNING_OBJECT (self, "Device index %d has empty id", i);
714 continue;
715 }
716
717 cur_device_name = convert_hstring_to_string (&name);
718 if (cur_device_name.empty ()) {
719 GST_WARNING_OBJECT (self, "Device index %d has empty device name", i);
720 continue;
721 }
722
723 GST_DEBUG_OBJECT (self, "device [%d] id: %s, name: %s",
724 device_index, cur_device_id.c_str (), cur_device_name.c_str ());
725
726 if (self->device_index < 0 && !self->device_id) {
727 GST_INFO_OBJECT (self, "Select the first device, device id %s",
728 cur_device_id.c_str ());
729 target_device_id_wstring = id.GetRawBuffer (nullptr);
730 target_device_id = cur_device_id;
731 target_device_name = cur_device_name;
732 break;
733 }
734
735 if (self->device_id &&
736 g_ascii_strcasecmp (self->device_id, cur_device_id.c_str ()) == 0) {
737 GST_INFO_OBJECT (self,
738 "Device index %d has matching device id %s", device_index,
739 cur_device_id.c_str ());
740 target_device_id_wstring = id.GetRawBuffer (nullptr);
741 target_device_id = cur_device_id;
742 target_device_name = cur_device_name;
743 break;
744 }
745
746 if (self->device_index >= 0 && self->device_index == device_index) {
747 GST_INFO_OBJECT (self, "Select device index %d, device id %s",
748 device_index, cur_device_id.c_str ());
749 target_device_id_wstring = id.GetRawBuffer (nullptr);
750 target_device_id = cur_device_id;
751 target_device_name = cur_device_name;
752 break;
753 }
754
755 /* count only available devices */
756 device_index++;
757 }
758
759 if (target_device_id_wstring.empty ()) {
760 GST_WARNING_OBJECT (self, "Couldn't find target device");
761 goto failed;
762 }
763
764 activate:
765 /* fill device id and name */
766 g_free (self->device_id);
767 self->device_id = g_strdup (target_device_id.c_str ());
768
769 g_free (self->device_name);
770 self->device_name = g_strdup (target_device_name.c_str ());
771
772 self->device_index = device_index;
773 /* default device supports automatic stream routing */
774 self->can_auto_routing = use_default_device;
775
776 hr = activator->ActivateDeviceAsync (target_device_id_wstring);
777 if (!gst_wasapi2_result (hr)) {
778 GST_WARNING_OBJECT (self, "Failed to activate device");
779 goto failed;
780 }
781
782 g_mutex_lock (&self->lock);
783 if (self->activate_state == GST_WASAPI2_CLIENT_ACTIVATE_INIT)
784 self->activate_state = GST_WASAPI2_CLIENT_ACTIVATE_WAIT;
785 g_mutex_unlock (&self->lock);
786
787 return TRUE;
788
789 failed:
790 self->activate_state = GST_WASAPI2_CLIENT_ACTIVATE_FAILED;
791
792 return FALSE;
793 }
794
795 static const gchar *
activate_state_to_string(GstWasapi2ClientActivateState state)796 activate_state_to_string (GstWasapi2ClientActivateState state)
797 {
798 switch (state) {
799 case GST_WASAPI2_CLIENT_ACTIVATE_FAILED:
800 return "FAILED";
801 case GST_WASAPI2_CLIENT_ACTIVATE_INIT:
802 return "INIT";
803 case GST_WASAPI2_CLIENT_ACTIVATE_WAIT:
804 return "WAIT";
805 case GST_WASAPI2_CLIENT_ACTIVATE_DONE:
806 return "DONE";
807 }
808
809 g_assert_not_reached ();
810
811 return "Undefined";
812 }
813
814 static gpointer
gst_wasapi2_client_thread_func(GstWasapi2Client * self)815 gst_wasapi2_client_thread_func (GstWasapi2Client * self)
816 {
817 RoInitializeWrapper initialize (RO_INIT_MULTITHREADED);
818 GSource *source;
819 HRESULT hr;
820 /* *INDENT-OFF* */
821 ComPtr<GstWasapiDeviceActivator> activator;
822
823 hr = MakeAndInitialize<GstWasapiDeviceActivator> (&activator,
824 self, self->dispatcher);
825 /* *INDENT-ON* */
826
827 if (!gst_wasapi2_result (hr)) {
828 GST_ERROR_OBJECT (self, "Could not create activator object");
829 self->activate_state = GST_WASAPI2_CLIENT_ACTIVATE_FAILED;
830 goto run_loop;
831 }
832
833 gst_wasapi2_client_activate_async (self, activator.Get ());
834
835 if (!self->dispatcher) {
836 /* In case that dispatcher is unavailable, wait activation synchroniously */
837 GST_DEBUG_OBJECT (self, "Wait device activation");
838 gst_wasapi2_client_ensure_activation (self);
839 GST_DEBUG_OBJECT (self, "Device activation result %s",
840 activate_state_to_string (self->activate_state));
841 }
842
843 run_loop:
844 g_main_context_push_thread_default (self->context);
845
846 source = g_idle_source_new ();
847 g_source_set_callback (source,
848 (GSourceFunc) gst_wasapi2_client_main_loop_running_cb, self, nullptr);
849 g_source_attach (source, self->context);
850 g_source_unref (source);
851
852 GST_DEBUG_OBJECT (self, "Starting main loop");
853 g_main_loop_run (self->loop);
854 GST_DEBUG_OBJECT (self, "Stopped main loop");
855
856 g_main_context_pop_thread_default (self->context);
857
858 GST_WASAPI2_CLEAR_COM (self->audio_client);
859
860 /* Reset explicitly to ensure that it happens before
861 * RoInitializeWrapper dtor is called */
862 activator.Reset ();
863
864 GST_DEBUG_OBJECT (self, "Exit thread function");
865
866 return nullptr;
867 }
868
869 GstCaps *
gst_wasapi2_client_get_caps(GstWasapi2Client * client)870 gst_wasapi2_client_get_caps (GstWasapi2Client * client)
871 {
872 WAVEFORMATEX *mix_format = nullptr;
873 static GstStaticCaps static_caps = GST_STATIC_CAPS (GST_WASAPI2_STATIC_CAPS);
874 GstCaps *scaps;
875 HRESULT hr;
876
877 g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), nullptr);
878
879 if (client->supported_caps)
880 return gst_caps_ref (client->supported_caps);
881
882 if (!client->audio_client) {
883 GST_WARNING_OBJECT (client, "IAudioClient3 wasn't configured");
884 return nullptr;
885 }
886
887 hr = client->audio_client->GetMixFormat (&mix_format);
888 if (!gst_wasapi2_result (hr)) {
889 GST_WARNING_OBJECT (client, "Failed to get mix format");
890 return nullptr;
891 }
892
893 scaps = gst_static_caps_get (&static_caps);
894 gst_wasapi2_util_parse_waveformatex (mix_format,
895 scaps, &client->supported_caps, nullptr);
896 gst_caps_unref (scaps);
897
898 CoTaskMemFree (mix_format);
899
900 if (!client->supported_caps) {
901 GST_ERROR_OBJECT (client, "No caps from subclass");
902 return nullptr;
903 }
904
905 return gst_caps_ref (client->supported_caps);
906 }
907
908 gboolean
gst_wasapi2_client_ensure_activation(GstWasapi2Client * client)909 gst_wasapi2_client_ensure_activation (GstWasapi2Client * client)
910 {
911 g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), FALSE);
912
913 /* should not happen */
914 g_assert (client->activate_state != GST_WASAPI2_CLIENT_ACTIVATE_INIT);
915
916 g_mutex_lock (&client->init_lock);
917 while (client->activate_state == GST_WASAPI2_CLIENT_ACTIVATE_WAIT)
918 g_cond_wait (&client->init_cond, &client->init_lock);
919 g_mutex_unlock (&client->init_lock);
920
921 return client->activate_state == GST_WASAPI2_CLIENT_ACTIVATE_DONE;
922 }
923
924 static HRESULT
find_dispatcher(ICoreDispatcher ** dispatcher)925 find_dispatcher (ICoreDispatcher ** dispatcher)
926 {
927 /* *INDENT-OFF* */
928 HStringReference hstr_core_app =
929 HStringReference(RuntimeClass_Windows_ApplicationModel_Core_CoreApplication);
930 ComPtr<ICoreApplication> core_app;
931 ComPtr<ICoreApplicationView> core_app_view;
932 ComPtr<ICoreWindow> core_window;
933 /* *INDENT-ON* */
934 HRESULT hr;
935
936 hr = GetActivationFactory (hstr_core_app.Get (), &core_app);
937 if (FAILED (hr))
938 return hr;
939
940 hr = core_app->GetCurrentView (&core_app_view);
941 if (FAILED (hr))
942 return hr;
943
944 hr = core_app_view->get_CoreWindow (&core_window);
945 if (FAILED (hr))
946 return hr;
947
948 return core_window->get_Dispatcher (dispatcher);
949 }
950
951 GstWasapi2Client *
gst_wasapi2_client_new(GstWasapi2ClientDeviceClass device_class,gint device_index,const gchar * device_id,gpointer dispatcher)952 gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class,
953 gint device_index, const gchar * device_id, gpointer dispatcher)
954 {
955 GstWasapi2Client *self;
956 /* *INDENT-OFF* */
957 ComPtr<ICoreDispatcher> core_dispatcher;
958 /* *INDENT-ON* */
959 /* Multiple COM init is allowed */
960 RoInitializeWrapper init_wrapper (RO_INIT_MULTITHREADED);
961
962 /* If application didn't pass ICoreDispatcher object,
963 * try to get dispatcher object for the current thread */
964 if (!dispatcher) {
965 HRESULT hr;
966
967 hr = find_dispatcher (&core_dispatcher);
968 if (SUCCEEDED (hr)) {
969 GST_DEBUG ("UI dispatcher is available");
970 dispatcher = core_dispatcher.Get ();
971 } else {
972 GST_DEBUG ("UI dispatcher is unavailable");
973 }
974 } else {
975 GST_DEBUG ("Use user passed UI dispatcher");
976 }
977
978 self = (GstWasapi2Client *) g_object_new (GST_TYPE_WASAPI2_CLIENT,
979 "device-class", device_class, "device-index", device_index,
980 "device", device_id, "dispatcher", dispatcher, nullptr);
981
982 /* Reset explicitly to ensure that it happens before
983 * RoInitializeWrapper dtor is called */
984 core_dispatcher.Reset ();
985
986 if (self->activate_state == GST_WASAPI2_CLIENT_ACTIVATE_FAILED) {
987 gst_object_unref (self);
988 return nullptr;
989 }
990
991 gst_object_ref_sink (self);
992
993 return self;
994 }
995
996 IAudioClient *
gst_wasapi2_client_get_handle(GstWasapi2Client * client)997 gst_wasapi2_client_get_handle (GstWasapi2Client * client)
998 {
999 g_return_val_if_fail (GST_IS_WASAPI2_CLIENT (client), nullptr);
1000
1001 return client->audio_client;
1002 }
1003