• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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