• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 
24 #include "gstasioobject.h"
25 #include <string.h>
26 #include <avrt.h>
27 #include <string>
28 #include <functional>
29 #include <vector>
30 #include <mutex>
31 #include <iasiodrv.h>
32 
33 GST_DEBUG_CATEGORY_STATIC (gst_asio_object_debug);
34 #define GST_CAT_DEFAULT gst_asio_object_debug
35 
36 /* List of GstAsioObject */
37 static GList *asio_object_list = nullptr;
38 
39 /* *INDENT-OFF* */
40 /* Protect asio_object_list and other global values */
41 std::mutex global_lock;
42 
43 /* Protect callback slots */
44 std::mutex slot_lock;
45 /* *INDENT-ON* */
46 
47 static void gst_asio_object_buffer_switch (GstAsioObject * self,
48     glong index, ASIOBool process_now);
49 static void gst_asio_object_sample_rate_changed (GstAsioObject * self,
50     ASIOSampleRate rate);
51 static glong gst_asio_object_messages (GstAsioObject * self, glong selector,
52     glong value, gpointer message, gdouble * opt);
53 static ASIOTime *gst_asio_object_buffer_switch_time_info (GstAsioObject * self,
54     ASIOTime * time_info, glong index, ASIOBool process_now);
55 
56 /* *INDENT-OFF* */
57 /* Object to delegate ASIO callbacks to dedicated GstAsioObject */
58 class GstAsioCallbacks
59 {
60 public:
GstAsioCallbacks(GstAsioObject * object)61   GstAsioCallbacks (GstAsioObject * object)
62   {
63     g_weak_ref_init (&object_, object);
64   }
65 
~GstAsioCallbacks()66   virtual ~GstAsioCallbacks ()
67   {
68     g_weak_ref_clear (&object_);
69   }
70 
BufferSwitch(glong index,ASIOBool process_now)71   void BufferSwitch (glong index, ASIOBool process_now)
72   {
73     GstAsioObject *obj = (GstAsioObject *) g_weak_ref_get (&object_);
74     if (!obj)
75       return;
76 
77     gst_asio_object_buffer_switch (obj, index, process_now);
78     gst_object_unref (obj);
79   }
80 
SampleRateChanged(ASIOSampleRate rate)81   void SampleRateChanged (ASIOSampleRate rate)
82   {
83     GstAsioObject *obj = (GstAsioObject *) g_weak_ref_get (&object_);
84     if (!obj)
85       return;
86 
87     gst_asio_object_sample_rate_changed (obj, rate);
88     gst_object_unref (obj);
89   }
90 
Messages(glong selector,glong value,gpointer message,gdouble * opt)91   glong Messages (glong selector, glong value, gpointer message, gdouble *opt)
92   {
93     GstAsioObject *obj = (GstAsioObject *) g_weak_ref_get (&object_);
94     if (!obj)
95       return 0;
96 
97     glong ret = gst_asio_object_messages (obj, selector, value, message, opt);
98     gst_object_unref (obj);
99 
100     return ret;
101   }
102 
BufferSwitchTimeInfo(ASIOTime * time_info,glong index,ASIOBool process_now)103   ASIOTime * BufferSwitchTimeInfo (ASIOTime * time_info,
104     glong index, ASIOBool process_now)
105   {
106     GstAsioObject *obj = (GstAsioObject *) g_weak_ref_get (&object_);
107     if (!obj)
108       return nullptr;
109 
110     ASIOTime * ret = gst_asio_object_buffer_switch_time_info (obj,
111         time_info, index, process_now);
112     gst_object_unref (obj);
113 
114     return ret;
115   }
116 
117 private:
118   GWeakRef object_;
119 };
120 
121 template <int instance_id>
122 class GstAsioCallbacksSlot
123 {
124 public:
125   static void
BufferSwitchStatic(glong index,ASIOBool process_now)126   BufferSwitchStatic(glong index, ASIOBool process_now)
127   {
128     buffer_switch(index, process_now);
129   }
130 
131   static void
SampleRateChangedStatic(ASIOSampleRate rate)132   SampleRateChangedStatic (ASIOSampleRate rate)
133   {
134     sample_rate_changed(rate);
135   }
136 
137   static glong
MessagesStatic(glong selector,glong value,gpointer message,gdouble * opt)138   MessagesStatic(glong selector, glong value, gpointer message, gdouble *opt)
139   {
140     return messages(selector, value, message, opt);
141   }
142 
143   static ASIOTime *
BufferSwitchTimeInfoStatic(ASIOTime * time_info,glong index,ASIOBool process_now)144   BufferSwitchTimeInfoStatic(ASIOTime * time_info, glong index,
145       ASIOBool process_now)
146   {
147     return buffer_switch_time_info(time_info, index, process_now);
148   }
149 
150   static std::function<void(glong, ASIOBool)> buffer_switch;
151   static std::function<void(ASIOSampleRate)> sample_rate_changed;
152   static std::function<glong(glong, glong, gpointer, gdouble *)> messages;
153   static std::function<ASIOTime *(ASIOTime *, glong, ASIOBool)> buffer_switch_time_info;
154 
155   static bool bound;
156 
Init()157   static void Init ()
158   {
159     buffer_switch = nullptr;
160     sample_rate_changed = nullptr;
161     messages = nullptr;
162     buffer_switch_time_info = nullptr;
163     bound = false;
164   }
165 
IsBound()166   static bool IsBound ()
167   {
168     return bound;
169   }
170 
Bind(GstAsioCallbacks * cb,ASIOCallbacks * driver_cb)171   static void Bind (GstAsioCallbacks * cb, ASIOCallbacks * driver_cb)
172   {
173     buffer_switch = std::bind(&GstAsioCallbacks::BufferSwitch, cb,
174       std::placeholders::_1, std::placeholders::_2);
175     sample_rate_changed = std::bind(&GstAsioCallbacks::SampleRateChanged, cb,
176       std::placeholders::_1);
177     messages = std::bind(&GstAsioCallbacks::Messages, cb,
178       std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
179       std::placeholders::_4);
180     buffer_switch_time_info = std::bind(&GstAsioCallbacks::BufferSwitchTimeInfo,
181       cb, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
182 
183     driver_cb->bufferSwitch = BufferSwitchStatic;
184     driver_cb->sampleRateDidChange = SampleRateChangedStatic;
185     driver_cb->asioMessage = MessagesStatic;
186     driver_cb->bufferSwitchTimeInfo = BufferSwitchTimeInfoStatic;
187 
188     bound = true;
189   }
190 };
191 
192 template <int instance_id>
193 std::function<void(glong, ASIOBool)> GstAsioCallbacksSlot<instance_id>::buffer_switch;
194 template <int instance_id>
195 std::function<void(ASIOSampleRate)> GstAsioCallbacksSlot<instance_id>::sample_rate_changed;
196 template <int instance_id>
197 std::function<glong(glong, glong, gpointer, gdouble *)> GstAsioCallbacksSlot<instance_id>::messages;
198 template <int instance_id>
199 std::function<ASIOTime *(ASIOTime *, glong, ASIOBool)> GstAsioCallbacksSlot<instance_id>::buffer_switch_time_info;
200 template <int instance_id>
201 bool GstAsioCallbacksSlot<instance_id>::bound;
202 
203 /* XXX: Create global slot objects,
204  * because ASIO callback doesn't support user data, hum.... */
205 GstAsioCallbacksSlot<0> cb_slot_0;
206 GstAsioCallbacksSlot<1> cb_slot_1;
207 GstAsioCallbacksSlot<2> cb_slot_2;
208 GstAsioCallbacksSlot<3> cb_slot_3;
209 GstAsioCallbacksSlot<4> cb_slot_4;
210 GstAsioCallbacksSlot<5> cb_slot_5;
211 GstAsioCallbacksSlot<6> cb_slot_6;
212 GstAsioCallbacksSlot<7> cb_slot_7;
213 
214 /* *INDENT-ON* */
215 
216 typedef struct
217 {
218   GstAsioObjectCallbacks callbacks;
219   guint64 callback_id;
220 } GstAsioObjectCallbacksPrivate;
221 
222 enum
223 {
224   PROP_0,
225   PROP_DEVICE_INFO,
226 };
227 
228 typedef enum
229 {
230   GST_ASIO_OBJECT_STATE_LOADED,
231   GST_ASIO_OBJECT_STATE_INITIALIZED,
232   GST_ASIO_OBJECT_STATE_PREPARED,
233   GST_ASIO_OBJECT_STATE_RUNNING,
234 } GstAsioObjectState;
235 
236 /* Protect singletone object */
237 struct _GstAsioObject
238 {
239   GstObject parent;
240 
241   GstAsioDeviceInfo *device_info;
242 
243   GstAsioObjectState state;
244 
245   IASIO *asio_handle;
246 
247   GThread *thread;
248   GMutex lock;
249   GCond cond;
250   GMainContext *context;
251   GMainLoop *loop;
252 
253   GMutex thread_lock;
254   GCond thread_cond;
255 
256   GMutex api_lock;
257 
258   /* called after init() done */
259   glong max_num_input_channels;
260   glong max_num_output_channels;
261 
262   glong min_buffer_size;
263   glong max_buffer_size;
264   glong preferred_buffer_size;
265   glong buffer_size_granularity;
266 
267   glong selected_buffer_size;
268 
269   /* List of supported sample rate */
270   GArray *supported_sample_rates;
271 
272   /* List of ASIOChannelInfo */
273   ASIOChannelInfo *input_channel_infos;
274   ASIOChannelInfo *output_channel_infos;
275 
276   /* Selected sample rate */
277   ASIOSampleRate sample_rate;
278 
279   /* Input/Output buffer infors */
280   ASIOBufferInfo *buffer_infos;
281 
282   /* Store requested channel before createbuffer */
283   gboolean *input_channel_requested;
284   gboolean *output_channel_requested;
285 
286   glong num_requested_input_channels;
287   glong num_requested_output_channels;
288   guint num_allocated_buffers;
289 
290   GList *src_client_callbacks;
291   GList *sink_client_callbacks;
292   GList *loopback_client_callbacks;
293   guint64 next_callback_id;
294 
295   GstAsioCallbacks *callbacks;
296   ASIOCallbacks driver_callbacks;
297   int slot_id;
298 
299   gboolean occupy_all_channels;
300 };
301 
302 static void gst_asio_object_constructed (GObject * object);
303 static void gst_asio_object_finalize (GObject * object);
304 static void gst_asio_object_set_property (GObject * object, guint prop_id,
305     const GValue * value, GParamSpec * pspec);
306 
307 static gpointer gst_asio_object_thread_func (GstAsioObject * self);
308 
309 #define gst_asio_object_parent_class parent_class
310 G_DEFINE_TYPE (GstAsioObject, gst_asio_object, GST_TYPE_OBJECT);
311 
312 static void
gst_asio_object_class_init(GstAsioObjectClass * klass)313 gst_asio_object_class_init (GstAsioObjectClass * klass)
314 {
315   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
316 
317   gobject_class->constructed = gst_asio_object_constructed;
318   gobject_class->finalize = gst_asio_object_finalize;
319   gobject_class->set_property = gst_asio_object_set_property;
320 
321   g_object_class_install_property (gobject_class, PROP_DEVICE_INFO,
322       g_param_spec_pointer ("device-info", "Device Info",
323           "A pointer to GstAsioDeviceInfo struct",
324           (GParamFlags) (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
325               G_PARAM_STATIC_STRINGS)));
326 
327   GST_DEBUG_CATEGORY_INIT (gst_asio_object_debug,
328       "asioobject", 0, "asioobject");
329 }
330 
331 static void
gst_asio_object_init(GstAsioObject * self)332 gst_asio_object_init (GstAsioObject * self)
333 {
334   g_mutex_init (&self->lock);
335   g_cond_init (&self->cond);
336 
337   g_mutex_init (&self->thread_lock);
338   g_cond_init (&self->thread_cond);
339 
340   g_mutex_init (&self->api_lock);
341 
342   self->supported_sample_rates = g_array_new (FALSE,
343       FALSE, sizeof (ASIOSampleRate));
344 
345   self->slot_id = -1;
346 }
347 
348 static void
gst_asio_object_constructed(GObject * object)349 gst_asio_object_constructed (GObject * object)
350 {
351   GstAsioObject *self = GST_ASIO_OBJECT (object);
352 
353   if (!self->device_info) {
354     GST_ERROR_OBJECT (self, "Device info was not configured");
355     return;
356   }
357 
358   self->context = g_main_context_new ();
359   self->loop = g_main_loop_new (self->context, FALSE);
360 
361   g_mutex_lock (&self->lock);
362   self->thread = g_thread_new ("GstAsioObject",
363       (GThreadFunc) gst_asio_object_thread_func, self);
364   while (!g_main_loop_is_running (self->loop))
365     g_cond_wait (&self->cond, &self->lock);
366   g_mutex_unlock (&self->lock);
367 }
368 
369 static void
gst_asio_object_finalize(GObject * object)370 gst_asio_object_finalize (GObject * object)
371 {
372   GstAsioObject *self = GST_ASIO_OBJECT (object);
373 
374   if (self->loop) {
375     g_main_loop_quit (self->loop);
376     g_thread_join (self->thread);
377     g_main_loop_unref (self->loop);
378     g_main_context_unref (self->context);
379   }
380 
381   g_mutex_clear (&self->lock);
382   g_cond_clear (&self->cond);
383 
384   g_mutex_clear (&self->thread_lock);
385   g_cond_clear (&self->thread_cond);
386 
387   g_mutex_clear (&self->api_lock);
388 
389   g_array_unref (self->supported_sample_rates);
390 
391   gst_asio_device_info_free (self->device_info);
392   g_free (self->input_channel_infos);
393   g_free (self->output_channel_infos);
394   g_free (self->input_channel_requested);
395   g_free (self->output_channel_requested);
396 
397   if (self->src_client_callbacks)
398     g_list_free_full (self->src_client_callbacks, (GDestroyNotify) g_free);
399   if (self->sink_client_callbacks)
400     g_list_free_full (self->sink_client_callbacks, (GDestroyNotify) g_free);
401   if (self->loopback_client_callbacks)
402     g_list_free_full (self->loopback_client_callbacks, (GDestroyNotify) g_free);
403 
404   G_OBJECT_CLASS (parent_class)->finalize (object);
405 }
406 
407 static void
gst_asio_object_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)408 gst_asio_object_set_property (GObject * object, guint prop_id,
409     const GValue * value, GParamSpec * pspec)
410 {
411   GstAsioObject *self = GST_ASIO_OBJECT (object);
412 
413   switch (prop_id) {
414     case PROP_DEVICE_INFO:
415       g_clear_pointer (&self->device_info, gst_asio_device_info_free);
416       self->device_info = gst_asio_device_info_copy ((GstAsioDeviceInfo *)
417           g_value_get_pointer (value));
418       break;
419     default:
420       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
421       break;
422   }
423 }
424 
425 static HWND
gst_asio_object_create_internal_hwnd(GstAsioObject * self)426 gst_asio_object_create_internal_hwnd (GstAsioObject * self)
427 {
428   WNDCLASSEXW wc;
429   ATOM atom = 0;
430   HINSTANCE hinstance = GetModuleHandle (NULL);
431 
432   atom = GetClassInfoExW (hinstance, L"GstAsioInternalWindow", &wc);
433   if (atom == 0) {
434     GST_LOG_OBJECT (self, "Register internal window class");
435     ZeroMemory (&wc, sizeof (WNDCLASSEX));
436 
437     wc.cbSize = sizeof (WNDCLASSEX);
438     wc.lpfnWndProc = DefWindowProc;
439     wc.hInstance = GetModuleHandle (nullptr);
440     wc.style = CS_OWNDC;
441     wc.lpszClassName = L"GstAsioInternalWindow";
442 
443     atom = RegisterClassExW (&wc);
444 
445     if (atom == 0) {
446       GST_ERROR_OBJECT (self, "Failed to register window class 0x%x",
447           (unsigned int) GetLastError ());
448       return nullptr;
449     }
450   }
451 
452   return CreateWindowExW (0, L"GstAsioInternalWindow", L"GstAsioInternal",
453       WS_POPUP, 0, 0, 1, 1, nullptr, nullptr, GetModuleHandle (nullptr),
454       nullptr);
455 }
456 
457 static gboolean
hwnd_msg_cb(GIOChannel * source,GIOCondition condition,gpointer data)458 hwnd_msg_cb (GIOChannel * source, GIOCondition condition, gpointer data)
459 {
460   MSG msg;
461 
462   if (!PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
463     return G_SOURCE_CONTINUE;
464 
465   TranslateMessage (&msg);
466   DispatchMessage (&msg);
467 
468   return G_SOURCE_CONTINUE;
469 }
470 
471 static gboolean
gst_asio_object_main_loop_running_cb(GstAsioObject * self)472 gst_asio_object_main_loop_running_cb (GstAsioObject * self)
473 {
474   GST_INFO_OBJECT (self, "Main loop running now");
475 
476   g_mutex_lock (&self->lock);
477   g_cond_signal (&self->cond);
478   g_mutex_unlock (&self->lock);
479 
480   return G_SOURCE_REMOVE;
481 }
482 
483 static gboolean
gst_asio_object_bind_callbacks(GstAsioObject * self)484 gst_asio_object_bind_callbacks (GstAsioObject * self)
485 {
486   std::lock_guard < std::mutex > lk (slot_lock);
487   gboolean ret = TRUE;
488 
489   if (!cb_slot_0.IsBound ()) {
490     cb_slot_0.Bind (self->callbacks, &self->driver_callbacks);
491     self->slot_id = 0;
492   } else if (!cb_slot_1.IsBound ()) {
493     cb_slot_1.Bind (self->callbacks, &self->driver_callbacks);
494     self->slot_id = 1;
495   } else if (!cb_slot_2.IsBound ()) {
496     cb_slot_2.Bind (self->callbacks, &self->driver_callbacks);
497     self->slot_id = 2;
498   } else if (!cb_slot_3.IsBound ()) {
499     cb_slot_3.Bind (self->callbacks, &self->driver_callbacks);
500     self->slot_id = 3;
501   } else if (!cb_slot_4.IsBound ()) {
502     cb_slot_4.Bind (self->callbacks, &self->driver_callbacks);
503     self->slot_id = 4;
504   } else if (!cb_slot_5.IsBound ()) {
505     cb_slot_5.Bind (self->callbacks, &self->driver_callbacks);
506     self->slot_id = 5;
507   } else if (!cb_slot_6.IsBound ()) {
508     cb_slot_6.Bind (self->callbacks, &self->driver_callbacks);
509     self->slot_id = 6;
510   } else if (!cb_slot_7.IsBound ()) {
511     cb_slot_7.Bind (self->callbacks, &self->driver_callbacks);
512     self->slot_id = 7;
513   } else {
514     self->slot_id = -1;
515     ret = FALSE;
516   }
517 
518   return ret;
519 }
520 
521 static void
gst_asio_object_unbind_callbacks(GstAsioObject * self)522 gst_asio_object_unbind_callbacks (GstAsioObject * self)
523 {
524   std::lock_guard < std::mutex > lk (slot_lock);
525 
526   if (!self->callbacks || self->slot_id < 0)
527     return;
528 
529   switch (self->slot_id) {
530     case 0:
531       cb_slot_0.Init ();
532       break;
533     case 1:
534       cb_slot_1.Init ();
535       break;
536     case 2:
537       cb_slot_2.Init ();
538       break;
539     case 3:
540       cb_slot_3.Init ();
541       break;
542     case 4:
543       cb_slot_4.Init ();
544       break;
545     case 5:
546       cb_slot_5.Init ();
547       break;
548     case 6:
549       cb_slot_6.Init ();
550       break;
551     case 7:
552       cb_slot_7.Init ();
553       break;
554     default:
555       g_assert_not_reached ();
556       break;
557   }
558 
559   return;
560 }
561 
562 static gpointer
gst_asio_object_thread_func(GstAsioObject * self)563 gst_asio_object_thread_func (GstAsioObject * self)
564 {
565   HANDLE avrt_handle = nullptr;
566   static DWORD task_idx = 0;
567   HWND hwnd;
568   GSource *source = nullptr;
569   GSource *hwnd_msg_source = nullptr;
570   GIOChannel *msg_io_channel = nullptr;
571   HRESULT hr;
572   ASIOError asio_rst;
573   IASIO *asio_handle = nullptr;
574   GstAsioDeviceInfo *device_info = self->device_info;
575   /* FIXME: check more sample rate */
576   static ASIOSampleRate sample_rate_to_check[] = {
577     48000.0, 44100.0, 192000.0, 96000.0, 88200.0,
578   };
579 
580   g_assert (device_info);
581 
582   GST_INFO_OBJECT (self,
583       "Enter loop, ThreadingModel: %s, driver-name: %s, driver-desc: %s",
584       device_info->sta_model ? "STA" : "MTA",
585       GST_STR_NULL (device_info->driver_name),
586       GST_STR_NULL (device_info->driver_desc));
587 
588   if (device_info->sta_model)
589     CoInitializeEx (NULL, COINIT_APARTMENTTHREADED);
590   else
591     CoInitializeEx (NULL, COINIT_MULTITHREADED);
592 
593   /* Our thread is unlikely different from driver's working thread though,
594    * let's do this. It should not cause any problem */
595   AvSetMmThreadCharacteristicsW (L"Pro Audio", &task_idx);
596   g_main_context_push_thread_default (self->context);
597 
598   source = g_idle_source_new ();
599   g_source_set_callback (source,
600       (GSourceFunc) gst_asio_object_main_loop_running_cb, self, nullptr);
601   g_source_attach (source, self->context);
602   g_source_unref (source);
603 
604   /* XXX: not sure why ASIO API wants Windows handle for init().
605    * Possibly it might be used for STA COM threading
606    * but it's undocummented... */
607   hwnd = gst_asio_object_create_internal_hwnd (self);
608   if (!hwnd)
609     goto run_loop;
610 
611   hr = CoCreateInstance (device_info->clsid, nullptr, CLSCTX_INPROC_SERVER,
612       device_info->clsid, (gpointer *) & asio_handle);
613   if (FAILED (hr)) {
614     GST_WARNING_OBJECT (self, "Failed to create IASIO instance, hr: 0x%x",
615         (guint) hr);
616     goto run_loop;
617   }
618 
619   if (!asio_handle->init (hwnd)) {
620     GST_WARNING_OBJECT (self, "Failed to init IASIO instance");
621     asio_handle->Release ();
622     asio_handle = nullptr;
623     goto run_loop;
624   }
625 
626   /* Query device information */
627   asio_rst = asio_handle->getChannels (&self->max_num_input_channels,
628       &self->max_num_output_channels);
629   if (asio_rst != 0) {
630     GST_WARNING_OBJECT (self, "Failed to query in/out channels, ret %ld",
631         asio_rst);
632     asio_handle->Release ();
633     asio_handle = nullptr;
634     goto run_loop;
635   }
636 
637   GST_INFO_OBJECT (self, "Input/Output channles: %ld/%ld",
638       self->max_num_input_channels, self->max_num_output_channels);
639 
640   asio_rst = asio_handle->getBufferSize (&self->min_buffer_size,
641       &self->max_buffer_size, &self->preferred_buffer_size,
642       &self->buffer_size_granularity);
643   if (asio_rst != 0) {
644     GST_WARNING_OBJECT (self, "Failed to get buffer size, ret %ld", asio_rst);
645     asio_handle->Release ();
646     asio_handle = nullptr;
647     goto run_loop;
648   }
649 
650   /* Use preferreed buffer size by default */
651   self->selected_buffer_size = self->preferred_buffer_size;
652 
653   GST_INFO_OBJECT (self, "min-buffer-size %ld, max-buffer-size %ld, "
654       "preferred-buffer-size %ld, buffer-size-granularity %ld",
655       self->min_buffer_size, self->max_buffer_size,
656       self->preferred_buffer_size, self->buffer_size_granularity);
657 
658   for (guint i = 0; i < G_N_ELEMENTS (sample_rate_to_check); i++) {
659     asio_rst = asio_handle->canSampleRate (sample_rate_to_check[i]);
660     if (asio_rst != 0)
661       continue;
662 
663     GST_INFO_OBJECT (self, "SampleRate %.1lf is supported",
664         sample_rate_to_check[i]);
665     g_array_append_val (self->supported_sample_rates, sample_rate_to_check[i]);
666   }
667 
668   if (self->supported_sample_rates->len == 0) {
669     GST_WARNING_OBJECT (self, "Failed to query supported sample rate");
670     asio_handle->Release ();
671     asio_handle = nullptr;
672     goto run_loop;
673   }
674 
675   /* Pick the first supported samplerate */
676   self->sample_rate =
677       g_array_index (self->supported_sample_rates, ASIOSampleRate, 0);
678   if (asio_handle->setSampleRate (self->sample_rate) != 0) {
679     GST_WARNING_OBJECT (self, "Failed to set samplerate %.1lf",
680         self->sample_rate);
681     asio_handle->Release ();
682     asio_handle = nullptr;
683     goto run_loop;
684   }
685 
686   if (self->max_num_input_channels > 0) {
687     self->input_channel_infos = g_new0 (ASIOChannelInfo,
688         self->max_num_input_channels);
689     for (glong i = 0; i < self->max_num_input_channels; i++) {
690       ASIOChannelInfo *info = &self->input_channel_infos[i];
691       info->channel = i;
692       info->isInput = TRUE;
693 
694       asio_rst = asio_handle->getChannelInfo (info);
695       if (asio_rst != 0) {
696         GST_WARNING_OBJECT (self, "Failed to %ld input channel info, ret %ld",
697             i, asio_rst);
698         asio_handle->Release ();
699         asio_handle = nullptr;
700         goto run_loop;
701       }
702 
703       GST_INFO_OBJECT (self,
704           "InputChannelInfo %ld: isActive %s, channelGroup %ld, "
705           "ASIOSampleType %ld, name %s", i, info->isActive ? "true" : "false",
706           info->channelGroup, info->type, GST_STR_NULL (info->name));
707     }
708 
709     self->input_channel_requested =
710         g_new0 (gboolean, self->max_num_input_channels);
711   }
712 
713   if (self->max_num_output_channels > 0) {
714     self->output_channel_infos = g_new0 (ASIOChannelInfo,
715         self->max_num_output_channels);
716     for (glong i = 0; i < self->max_num_output_channels; i++) {
717       ASIOChannelInfo *info = &self->output_channel_infos[i];
718       info->channel = i;
719       info->isInput = FALSE;
720 
721       asio_rst = asio_handle->getChannelInfo (info);
722       if (asio_rst != 0) {
723         GST_WARNING_OBJECT (self, "Failed to %ld output channel info, ret %ld",
724             i, asio_rst);
725         asio_handle->Release ();
726         asio_handle = nullptr;
727         goto run_loop;
728       }
729 
730       GST_INFO_OBJECT (self,
731           "OutputChannelInfo %ld: isActive %s, channelGroup %ld, "
732           "ASIOSampleType %ld, name %s", i, info->isActive ? "true" : "false",
733           info->channelGroup, info->type, GST_STR_NULL (info->name));
734     }
735 
736     self->output_channel_requested =
737         g_new0 (gboolean, self->max_num_input_channels);
738   }
739 
740   asio_rst = asio_handle->getSampleRate (&self->sample_rate);
741   if (asio_rst != 0) {
742     GST_WARNING_OBJECT (self,
743         "Failed to get current samplerate, ret %ld", asio_rst);
744     asio_handle->Release ();
745     asio_handle = nullptr;
746     goto run_loop;
747   }
748 
749   GST_INFO_OBJECT (self, "Current samplerate %.1lf", self->sample_rate);
750 
751   self->callbacks = new GstAsioCallbacks (self);
752   if (!gst_asio_object_bind_callbacks (self)) {
753     GST_ERROR_OBJECT (self, "Failed to bind callback to slot");
754     delete self->callbacks;
755     self->callbacks = nullptr;
756 
757     asio_handle->Release ();
758     asio_handle = nullptr;
759     goto run_loop;
760   }
761 
762   msg_io_channel = g_io_channel_win32_new_messages ((guintptr) hwnd);
763   hwnd_msg_source = g_io_create_watch (msg_io_channel, G_IO_IN);
764   g_source_set_callback (hwnd_msg_source, (GSourceFunc) hwnd_msg_cb,
765       self->context, nullptr);
766   g_source_attach (hwnd_msg_source, self->context);
767 
768   self->state = GST_ASIO_OBJECT_STATE_INITIALIZED;
769   self->asio_handle = asio_handle;
770 
771 run_loop:
772   g_main_loop_run (self->loop);
773 
774   if (self->asio_handle) {
775     if (self->state > GST_ASIO_OBJECT_STATE_PREPARED)
776       self->asio_handle->stop ();
777 
778     if (self->state > GST_ASIO_OBJECT_STATE_INITIALIZED)
779       self->asio_handle->disposeBuffers ();
780   }
781 
782   gst_asio_object_unbind_callbacks (self);
783   if (self->callbacks) {
784     delete self->callbacks;
785     self->callbacks = nullptr;
786   }
787 
788   if (hwnd_msg_source) {
789     g_source_destroy (hwnd_msg_source);
790     g_source_unref (hwnd_msg_source);
791   }
792 
793   if (msg_io_channel)
794     g_io_channel_unref (msg_io_channel);
795 
796   if (hwnd)
797     DestroyWindow (hwnd);
798 
799   g_main_context_pop_thread_default (self->context);
800 
801   if (avrt_handle)
802     AvRevertMmThreadCharacteristics (avrt_handle);
803 
804   if (asio_handle) {
805     asio_handle->Release ();
806     asio_handle = nullptr;
807   }
808 
809   CoUninitialize ();
810 
811   GST_INFO_OBJECT (self, "Exit loop");
812 
813   return nullptr;
814 }
815 
816 static void
gst_asio_object_weak_ref_notify(gpointer data,GstAsioObject * object)817 gst_asio_object_weak_ref_notify (gpointer data, GstAsioObject * object)
818 {
819   std::lock_guard < std::mutex > lk (global_lock);
820   asio_object_list = g_list_remove (asio_object_list, object);
821 }
822 
823 GstAsioObject *
gst_asio_object_new(const GstAsioDeviceInfo * info,gboolean occupy_all_channels)824 gst_asio_object_new (const GstAsioDeviceInfo * info,
825     gboolean occupy_all_channels)
826 {
827   GstAsioObject *self = nullptr;
828   GList *iter;
829   std::lock_guard < std::mutex > lk (global_lock);
830 
831   g_return_val_if_fail (info != nullptr, nullptr);
832 
833   /* Check if we have object corresponding to CLSID, and if so return
834    * already existing object instead of allocating new one */
835   for (iter = asio_object_list; iter; iter = g_list_next (iter)) {
836     GstAsioObject *object = (GstAsioObject *) iter->data;
837 
838     if (object->device_info->clsid == info->clsid) {
839       GST_DEBUG_OBJECT (object, "Found configured ASIO object");
840       self = (GstAsioObject *) gst_object_ref (object);
841       break;
842     }
843   }
844 
845   if (self)
846     return self;
847 
848   self = (GstAsioObject *) g_object_new (GST_TYPE_ASIO_OBJECT,
849       "device-info", info, nullptr);
850 
851   if (!self->asio_handle) {
852     GST_WARNING_OBJECT (self, "ASIO handle is not available");
853     gst_object_unref (self);
854 
855     return nullptr;
856   }
857 
858   self->occupy_all_channels = occupy_all_channels;
859 
860   gst_object_ref_sink (self);
861 
862   g_object_weak_ref (G_OBJECT (self),
863       (GWeakNotify) gst_asio_object_weak_ref_notify, nullptr);
864   asio_object_list = g_list_append (asio_object_list, self);
865 
866   return self;
867 }
868 
869 static GstCaps *
gst_asio_object_create_caps_from_channel_info(GstAsioObject * self,ASIOChannelInfo * info,guint min_num_channels,guint max_num_channels)870 gst_asio_object_create_caps_from_channel_info (GstAsioObject * self,
871     ASIOChannelInfo * info, guint min_num_channels, guint max_num_channels)
872 {
873   GstCaps *caps;
874   std::string caps_str;
875   GstAudioFormat fmt;
876   const gchar *fmt_str;
877 
878   g_assert (info);
879   g_assert (max_num_channels >= min_num_channels);
880 
881   fmt = gst_asio_sample_type_to_gst (info->type);
882   if (fmt == GST_AUDIO_FORMAT_UNKNOWN) {
883     GST_ERROR_OBJECT (self, "Unknown format");
884     return nullptr;
885   }
886 
887   fmt_str = gst_audio_format_to_string (fmt);
888 
889   /* Actually we are non-interleaved, but element will interlave data */
890   caps_str = "audio/x-raw, layout = (string) interleaved, ";
891   caps_str += "format = (string) " + std::string (fmt_str) + ", ";
892   /* use fixated sample rate, otherwise get_caps/set_sample_rate() might
893    * be racy in case that multiple sink/src are used */
894   caps_str +=
895       "rate = (int) " + std::to_string ((gint) self->sample_rate) + ", ";
896 
897   if (max_num_channels == min_num_channels)
898     caps_str += "channels = (int) " + std::to_string (max_num_channels);
899   else
900     caps_str += "channels = (int) [ " + std::to_string (min_num_channels) +
901         ", " + std::to_string (max_num_channels) + " ]";
902 
903   caps = gst_caps_from_string (caps_str.c_str ());
904   if (!caps) {
905     GST_ERROR_OBJECT (self, "Failed to create caps");
906     return nullptr;
907   }
908 
909   GST_DEBUG_OBJECT (self, "Create caps %" GST_PTR_FORMAT, caps);
910 
911   return caps;
912 }
913 
914 /* FIXME: assuming all channels has the same format but it might not be true? */
915 GstCaps *
gst_asio_object_get_caps(GstAsioObject * obj,GstAsioDeviceClassType type,guint min_num_channels,guint max_num_channels)916 gst_asio_object_get_caps (GstAsioObject * obj, GstAsioDeviceClassType type,
917     guint min_num_channels, guint max_num_channels)
918 {
919   ASIOChannelInfo *infos;
920 
921   g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), nullptr);
922 
923   if (type == GST_ASIO_DEVICE_CLASS_CAPTURE) {
924     if (obj->max_num_input_channels == 0) {
925       GST_WARNING_OBJECT (obj, "Device doesn't support input");
926       return nullptr;
927     }
928 
929     /* max_num_channels == 0 means [1, max-allowed-channles] */
930     if (max_num_channels > 0) {
931       if (max_num_channels > obj->max_num_input_channels) {
932         GST_WARNING_OBJECT (obj, "Too many max channels");
933         return nullptr;
934       }
935     } else {
936       max_num_channels = obj->max_num_input_channels;
937     }
938 
939     if (min_num_channels > 0) {
940       if (min_num_channels > obj->max_num_input_channels) {
941         GST_WARNING_OBJECT (obj, "Too many min channels");
942         return nullptr;
943       }
944     } else {
945       min_num_channels = 1;
946     }
947 
948     infos = obj->input_channel_infos;
949   } else {
950     if (obj->max_num_output_channels == 0) {
951       GST_WARNING_OBJECT (obj, "Device doesn't support output");
952       return nullptr;
953     }
954 
955     /* max_num_channels == 0 means [1, max-allowed-channles] */
956     if (max_num_channels > 0) {
957       if (max_num_channels > obj->max_num_output_channels) {
958         GST_WARNING_OBJECT (obj, "Too many max channels");
959         return nullptr;
960       }
961     } else {
962       max_num_channels = obj->max_num_output_channels;
963     }
964 
965     if (min_num_channels > 0) {
966       if (min_num_channels > obj->max_num_output_channels) {
967         GST_WARNING_OBJECT (obj, "Too many min channels");
968         return nullptr;
969       }
970     } else {
971       min_num_channels = 1;
972     }
973 
974     infos = obj->output_channel_infos;
975   }
976 
977   return gst_asio_object_create_caps_from_channel_info (obj,
978       infos, min_num_channels, max_num_channels);
979 }
980 
981 gboolean
gst_asio_object_get_max_num_channels(GstAsioObject * obj,glong * num_input_ch,glong * num_output_ch)982 gst_asio_object_get_max_num_channels (GstAsioObject * obj, glong * num_input_ch,
983     glong * num_output_ch)
984 {
985   g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
986 
987   if (num_input_ch)
988     *num_input_ch = obj->max_num_input_channels;
989   if (num_output_ch)
990     *num_output_ch = obj->max_num_output_channels;
991 
992   return TRUE;
993 }
994 
995 gboolean
gst_asio_object_get_buffer_size(GstAsioObject * obj,glong * min_size,glong * max_size,glong * preferred_size,glong * granularity)996 gst_asio_object_get_buffer_size (GstAsioObject * obj, glong * min_size,
997     glong * max_size, glong * preferred_size, glong * granularity)
998 {
999   g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
1000 
1001   if (min_size)
1002     *min_size = obj->min_buffer_size;
1003   if (max_size)
1004     *max_size = obj->max_buffer_size;
1005   if (preferred_size)
1006     *preferred_size = obj->preferred_buffer_size;
1007   if (granularity)
1008     *granularity = obj->buffer_size_granularity;
1009 
1010   return TRUE;
1011 }
1012 
1013 typedef void (*GstAsioObjectThreadFunc) (GstAsioObject * obj, gpointer data);
1014 
1015 typedef struct
1016 {
1017   GstAsioObject *self;
1018   GstAsioObjectThreadFunc func;
1019   gpointer data;
1020   gboolean fired;
1021 } GstAsioObjectThreadRunData;
1022 
1023 static gboolean
gst_asio_object_thread_run_func(GstAsioObjectThreadRunData * data)1024 gst_asio_object_thread_run_func (GstAsioObjectThreadRunData * data)
1025 {
1026   GstAsioObject *self = data->self;
1027 
1028   if (data->func)
1029     data->func (self, data->data);
1030 
1031   g_mutex_lock (&self->thread_lock);
1032   data->fired = TRUE;
1033   g_cond_broadcast (&self->thread_cond);
1034   g_mutex_unlock (&self->thread_lock);
1035 
1036   return G_SOURCE_REMOVE;
1037 }
1038 
1039 static void
gst_asio_object_thread_add(GstAsioObject * self,GstAsioObjectThreadFunc func,gpointer data)1040 gst_asio_object_thread_add (GstAsioObject * self, GstAsioObjectThreadFunc func,
1041     gpointer data)
1042 {
1043   GstAsioObjectThreadRunData thread_data;
1044 
1045   g_return_if_fail (GST_IS_ASIO_OBJECT (self));
1046 
1047   thread_data.self = self;
1048   thread_data.func = func;
1049   thread_data.data = data;
1050   thread_data.fired = FALSE;
1051 
1052   g_main_context_invoke (self->context,
1053       (GSourceFunc) gst_asio_object_thread_run_func, &thread_data);
1054 
1055   g_mutex_lock (&self->thread_lock);
1056   while (!thread_data.fired)
1057     g_cond_wait (&self->thread_cond, &self->thread_lock);
1058   g_mutex_unlock (&self->thread_lock);
1059 }
1060 
1061 static gboolean
gst_asio_object_validate_channels(GstAsioObject * self,gboolean is_input,guint * channel_indices,guint num_channels)1062 gst_asio_object_validate_channels (GstAsioObject * self, gboolean is_input,
1063     guint * channel_indices, guint num_channels)
1064 {
1065   if (is_input) {
1066     if (self->max_num_input_channels < num_channels) {
1067       GST_WARNING_OBJECT (self, "%d exceeds max input channels %ld",
1068           num_channels, self->max_num_input_channels);
1069       return FALSE;
1070     }
1071 
1072     for (guint i = 0; i < num_channels; i++) {
1073       guint ch = channel_indices[i];
1074       if (self->max_num_input_channels <= ch) {
1075         GST_WARNING_OBJECT (self, "%d exceeds max input channels %ld",
1076             ch, self->max_num_input_channels);
1077 
1078         return FALSE;
1079       }
1080     }
1081   } else {
1082     if (self->max_num_output_channels < num_channels) {
1083       GST_WARNING_OBJECT (self, "%d exceeds max output channels %ld",
1084           num_channels, self->max_num_output_channels);
1085 
1086       return FALSE;
1087     }
1088 
1089     for (guint i = 0; i < num_channels; i++) {
1090       guint ch = channel_indices[i];
1091       if (self->max_num_output_channels <= ch) {
1092         GST_WARNING_OBJECT (self, "%d exceeds max output channels %ld",
1093             ch, self->max_num_output_channels);
1094 
1095         return FALSE;
1096       }
1097     }
1098   }
1099 
1100   return TRUE;
1101 }
1102 
1103 static gboolean
gst_asio_object_check_buffer_reuse(GstAsioObject * self,ASIOBool is_input,guint * channel_indices,guint num_channels)1104 gst_asio_object_check_buffer_reuse (GstAsioObject * self, ASIOBool is_input,
1105     guint * channel_indices, guint num_channels)
1106 {
1107   guint num_found = 0;
1108 
1109   g_assert (self->buffer_infos);
1110   g_assert (self->num_allocated_buffers > 0);
1111 
1112   for (guint i = 0; i < self->num_allocated_buffers; i++) {
1113     ASIOBufferInfo *info = &self->buffer_infos[i];
1114 
1115     if (info->isInput != is_input)
1116       continue;
1117 
1118     for (guint j = 0; j < num_channels; j++) {
1119       if (info->channelNum == channel_indices[j]) {
1120         num_found++;
1121 
1122         break;
1123       }
1124     }
1125   }
1126 
1127   return num_found == num_channels;
1128 }
1129 
1130 static void
gst_asio_object_dispose_buffers_async(GstAsioObject * self,ASIOError * rst)1131 gst_asio_object_dispose_buffers_async (GstAsioObject * self, ASIOError * rst)
1132 {
1133   g_assert (self->asio_handle);
1134   g_assert (rst);
1135 
1136   *rst = self->asio_handle->disposeBuffers ();
1137 }
1138 
1139 static gboolean
gst_asio_object_dispose_buffers(GstAsioObject * self)1140 gst_asio_object_dispose_buffers (GstAsioObject * self)
1141 {
1142   ASIOError rst;
1143   g_assert (self->asio_handle);
1144 
1145   if (!self->buffer_infos)
1146     return TRUE;
1147 
1148   if (!self->device_info->sta_model) {
1149     rst = self->asio_handle->disposeBuffers ();
1150   } else {
1151     gst_asio_object_thread_add (self,
1152         (GstAsioObjectThreadFunc) gst_asio_object_dispose_buffers_async, &rst);
1153   }
1154 
1155   g_clear_pointer (&self->buffer_infos, g_free);
1156   self->num_allocated_buffers = 0;
1157 
1158   return rst == 0;
1159 }
1160 
1161 static ASIOError
gst_asio_object_create_buffers_real(GstAsioObject * self,glong * buffer_size)1162 gst_asio_object_create_buffers_real (GstAsioObject * self, glong * buffer_size)
1163 {
1164   ASIOError err;
1165 
1166   g_assert (buffer_size);
1167 
1168   err = self->asio_handle->createBuffers (self->buffer_infos,
1169       self->num_requested_input_channels + self->num_requested_output_channels,
1170       *buffer_size, &self->driver_callbacks);
1171 
1172   /* It failed and buffer size is not equal to preferred size,
1173    * try again with preferred size */
1174   if (err != 0 && *buffer_size != self->preferred_buffer_size) {
1175     GST_WARNING_OBJECT (self,
1176         "Failed to create buffer with buffer size %ld, try again with %ld",
1177         *buffer_size, self->preferred_buffer_size);
1178 
1179     err = self->asio_handle->createBuffers (self->buffer_infos,
1180         self->num_requested_input_channels +
1181         self->num_requested_output_channels, self->preferred_buffer_size,
1182         &self->driver_callbacks);
1183 
1184     if (!err) {
1185       *buffer_size = self->preferred_buffer_size;
1186     }
1187   }
1188 
1189   return err;
1190 }
1191 
1192 typedef struct
1193 {
1194   glong buffer_size;
1195   ASIOError err;
1196 } CreateBuffersAsyncData;
1197 
1198 static void
gst_asio_object_create_buffers_async(GstAsioObject * self,CreateBuffersAsyncData * data)1199 gst_asio_object_create_buffers_async (GstAsioObject * self,
1200     CreateBuffersAsyncData * data)
1201 {
1202   data->err = gst_asio_object_create_buffers_real (self, &data->buffer_size);
1203 }
1204 
1205 static gboolean
gst_asio_object_create_buffers_internal(GstAsioObject * self,glong * buffer_size)1206 gst_asio_object_create_buffers_internal (GstAsioObject * self,
1207     glong * buffer_size)
1208 {
1209   ASIOError err;
1210   g_assert (self->asio_handle);
1211 
1212   if (!self->device_info->sta_model) {
1213     err = gst_asio_object_create_buffers_real (self, buffer_size);
1214   } else {
1215     CreateBuffersAsyncData data;
1216     data.buffer_size = *buffer_size;
1217 
1218     gst_asio_object_thread_add (self,
1219         (GstAsioObjectThreadFunc) gst_asio_object_create_buffers_async, &data);
1220 
1221     err = data.err;
1222     *buffer_size = data.buffer_size;
1223   }
1224 
1225   return !err;
1226 }
1227 
1228 gboolean
gst_asio_object_create_buffers(GstAsioObject * obj,GstAsioDeviceClassType type,guint * channel_indices,guint num_channels,guint * buffer_size)1229 gst_asio_object_create_buffers (GstAsioObject * obj,
1230     GstAsioDeviceClassType type,
1231     guint * channel_indices, guint num_channels, guint * buffer_size)
1232 {
1233   gboolean can_reuse = FALSE;
1234   guint i, j;
1235   glong buf_size;
1236   glong prev_buf_size = 0;
1237   gboolean is_src;
1238 
1239   g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
1240   g_return_val_if_fail (channel_indices != nullptr, FALSE);
1241   g_return_val_if_fail (num_channels > 0, FALSE);
1242 
1243   GST_DEBUG_OBJECT (obj, "Create buffers");
1244 
1245   if (type == GST_ASIO_DEVICE_CLASS_CAPTURE)
1246     is_src = TRUE;
1247   else
1248     is_src = FALSE;
1249 
1250   g_mutex_lock (&obj->api_lock);
1251   if (!gst_asio_object_validate_channels (obj, is_src, channel_indices,
1252           num_channels)) {
1253     GST_ERROR_OBJECT (obj, "Invalid request");
1254     g_mutex_unlock (&obj->api_lock);
1255 
1256     return FALSE;
1257   }
1258 
1259   if (obj->buffer_infos) {
1260     GST_DEBUG_OBJECT (obj,
1261         "Have configured buffer infors, checking whether we can reuse it");
1262     can_reuse = gst_asio_object_check_buffer_reuse (obj,
1263         is_src ? TRUE : FALSE, channel_indices, num_channels);
1264   }
1265 
1266   if (can_reuse) {
1267     GST_DEBUG_OBJECT (obj, "We can reuse already allocated buffers");
1268     if (buffer_size)
1269       *buffer_size = obj->selected_buffer_size;
1270 
1271     g_mutex_unlock (&obj->api_lock);
1272 
1273     return TRUE;
1274   }
1275 
1276   /* Cannot re-allocated buffers once started... */
1277   if (obj->state > GST_ASIO_OBJECT_STATE_PREPARED) {
1278     GST_WARNING_OBJECT (obj, "We are running already");
1279     g_mutex_unlock (&obj->api_lock);
1280 
1281     return FALSE;
1282   }
1283 
1284   /* Use already configured buffer size */
1285   if (obj->buffer_infos)
1286     prev_buf_size = obj->selected_buffer_size;
1287 
1288   /* If we have configured buffers, dispose and re-allocate */
1289   if (!gst_asio_object_dispose_buffers (obj)) {
1290     GST_ERROR_OBJECT (obj, "Failed to dispose buffers");
1291 
1292     obj->state = GST_ASIO_OBJECT_STATE_INITIALIZED;
1293 
1294     g_mutex_unlock (&obj->api_lock);
1295     return FALSE;
1296   }
1297 
1298   if (obj->occupy_all_channels) {
1299     GST_INFO_OBJECT (obj,
1300         "occupy-all-channels mode, will allocate buffers for all channels");
1301     /* In this case, we will allocate buffer for all available input/output
1302      * channles, regardless of what requested here */
1303     for (guint i = 0; i < (guint) obj->max_num_input_channels; i++)
1304       obj->input_channel_requested[i] = TRUE;
1305     for (guint i = 0; i < (guint) obj->max_num_output_channels; i++)
1306       obj->output_channel_requested[i] = TRUE;
1307 
1308     obj->num_requested_input_channels = obj->max_num_input_channels;
1309     obj->num_requested_output_channels = obj->max_num_output_channels;
1310   } else {
1311     if (is_src) {
1312       for (guint i = 0; i < num_channels; i++) {
1313         guint ch = channel_indices[i];
1314 
1315         obj->input_channel_requested[ch] = TRUE;
1316       }
1317 
1318       obj->num_requested_input_channels = 0;
1319       for (guint i = 0; i < obj->max_num_input_channels; i++) {
1320         if (obj->input_channel_requested[i])
1321           obj->num_requested_input_channels++;
1322       }
1323     } else {
1324       for (guint i = 0; i < num_channels; i++) {
1325         guint ch = channel_indices[i];
1326 
1327         obj->output_channel_requested[ch] = TRUE;
1328       }
1329 
1330       obj->num_requested_output_channels = 0;
1331       for (guint i = 0; i < obj->max_num_output_channels; i++) {
1332         if (obj->output_channel_requested[i])
1333           obj->num_requested_output_channels++;
1334       }
1335     }
1336   }
1337 
1338   obj->num_allocated_buffers = obj->num_requested_input_channels +
1339       obj->num_requested_output_channels;
1340 
1341   obj->buffer_infos = g_new0 (ASIOBufferInfo, obj->num_allocated_buffers);
1342   for (i = 0, j = 0; i < obj->num_requested_input_channels; i++) {
1343     ASIOBufferInfo *info = &obj->buffer_infos[i];
1344 
1345     info->isInput = TRUE;
1346     while (!obj->input_channel_requested[j])
1347       j++;
1348 
1349     info->channelNum = j;
1350     j++;
1351   }
1352 
1353   for (i = obj->num_requested_input_channels, j = 0;
1354       i <
1355       obj->num_requested_input_channels + obj->num_requested_output_channels;
1356       i++) {
1357     ASIOBufferInfo *info = &obj->buffer_infos[i];
1358 
1359     info->isInput = FALSE;
1360     while (!obj->output_channel_requested[j])
1361       j++;
1362 
1363     info->channelNum = j;
1364     j++;
1365   }
1366 
1367   if (prev_buf_size > 0) {
1368     buf_size = prev_buf_size;
1369   } else if (buffer_size && *buffer_size > 0) {
1370     buf_size = *buffer_size;
1371   } else {
1372     buf_size = obj->preferred_buffer_size;
1373   }
1374 
1375   GST_INFO_OBJECT (obj, "Creating buffer with size %ld", buf_size);
1376 
1377   if (!gst_asio_object_create_buffers_internal (obj, &buf_size)) {
1378     GST_ERROR_OBJECT (obj, "Failed to create buffers");
1379     g_clear_pointer (&obj->buffer_infos, g_free);
1380     obj->num_allocated_buffers = 0;
1381 
1382     obj->state = GST_ASIO_OBJECT_STATE_INITIALIZED;
1383 
1384     g_mutex_unlock (&obj->api_lock);
1385 
1386     return FALSE;
1387   }
1388 
1389   GST_INFO_OBJECT (obj, "Selected buffer size %ld", buf_size);
1390 
1391   obj->selected_buffer_size = buf_size;
1392   if (buffer_size)
1393     *buffer_size = buf_size;
1394 
1395   obj->state = GST_ASIO_OBJECT_STATE_PREPARED;
1396 
1397   g_mutex_unlock (&obj->api_lock);
1398 
1399   return TRUE;
1400 }
1401 
1402 typedef struct
1403 {
1404   glong arg[4];
1405   ASIOError ret;
1406 } RunAsyncData;
1407 
1408 static void
gst_asio_object_get_latencies_async(GstAsioObject * self,RunAsyncData * data)1409 gst_asio_object_get_latencies_async (GstAsioObject * self, RunAsyncData * data)
1410 {
1411   data->ret = self->asio_handle->getLatencies (&data->arg[0], &data->arg[1]);
1412 }
1413 
1414 gboolean
gst_asio_object_get_latencies(GstAsioObject * obj,glong * input_latency,glong * output_latency)1415 gst_asio_object_get_latencies (GstAsioObject * obj, glong * input_latency,
1416     glong * output_latency)
1417 {
1418   RunAsyncData data = { 0 };
1419   ASIOError err;
1420 
1421   g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
1422   g_assert (obj->asio_handle);
1423 
1424   if (!obj->device_info->sta_model) {
1425     err = obj->asio_handle->getLatencies (input_latency, output_latency);
1426   } else {
1427     gst_asio_object_thread_add (obj,
1428         (GstAsioObjectThreadFunc) gst_asio_object_get_latencies_async, &data);
1429 
1430     *input_latency = data.arg[0];
1431     *output_latency = data.arg[1];
1432     err = data.ret;
1433   }
1434 
1435   return !err;
1436 }
1437 
1438 typedef struct
1439 {
1440   ASIOSampleRate sample_rate;
1441   ASIOError err;
1442 } SampleRateAsyncData;
1443 
1444 static void
gst_asio_object_can_sample_rate_async(GstAsioObject * self,SampleRateAsyncData * data)1445 gst_asio_object_can_sample_rate_async (GstAsioObject * self,
1446     SampleRateAsyncData * data)
1447 {
1448   data->err = self->asio_handle->canSampleRate (data->sample_rate);
1449 }
1450 
1451 gboolean
gst_asio_object_can_sample_rate(GstAsioObject * obj,ASIOSampleRate sample_rate)1452 gst_asio_object_can_sample_rate (GstAsioObject * obj,
1453     ASIOSampleRate sample_rate)
1454 {
1455   SampleRateAsyncData data = { 0 };
1456   ASIOError err = 0;
1457 
1458   g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
1459   g_assert (obj->asio_handle);
1460 
1461   g_mutex_lock (&obj->api_lock);
1462   for (guint i = 0; i < obj->supported_sample_rates->len; i++) {
1463     ASIOSampleRate val = g_array_index (obj->supported_sample_rates,
1464         ASIOSampleRate, i);
1465     if (val == sample_rate) {
1466       g_mutex_unlock (&obj->api_lock);
1467       return TRUE;
1468     }
1469   }
1470 
1471   if (!obj->device_info->sta_model) {
1472     err = obj->asio_handle->canSampleRate (sample_rate);
1473 
1474     if (!err)
1475       g_array_append_val (obj->supported_sample_rates, sample_rate);
1476 
1477     g_mutex_unlock (&obj->api_lock);
1478     return !err;
1479   }
1480 
1481   data.sample_rate = sample_rate;
1482   gst_asio_object_thread_add (obj,
1483       (GstAsioObjectThreadFunc) gst_asio_object_can_sample_rate_async, &data);
1484 
1485   if (!data.err)
1486     g_array_append_val (obj->supported_sample_rates, sample_rate);
1487 
1488   g_mutex_unlock (&obj->api_lock);
1489 
1490   return !data.err;
1491 }
1492 
1493 gboolean
gst_asio_object_get_sample_rate(GstAsioObject * obj,ASIOSampleRate * sample_rate)1494 gst_asio_object_get_sample_rate (GstAsioObject * obj,
1495     ASIOSampleRate * sample_rate)
1496 {
1497   g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
1498 
1499   *sample_rate = obj->sample_rate;
1500 
1501   return 0;
1502 }
1503 
1504 static void
gst_asio_object_set_sample_rate_async(GstAsioObject * self,SampleRateAsyncData * data)1505 gst_asio_object_set_sample_rate_async (GstAsioObject * self,
1506     SampleRateAsyncData * data)
1507 {
1508   data->err = self->asio_handle->setSampleRate (data->sample_rate);
1509   if (!data->err)
1510     self->sample_rate = data->sample_rate;
1511 }
1512 
1513 gboolean
gst_asio_object_set_sample_rate(GstAsioObject * obj,ASIOSampleRate sample_rate)1514 gst_asio_object_set_sample_rate (GstAsioObject * obj,
1515     ASIOSampleRate sample_rate)
1516 {
1517   SampleRateAsyncData data = { 0 };
1518   ASIOError err = 0;
1519 
1520   g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
1521   g_assert (obj->asio_handle);
1522 
1523   g_mutex_lock (&obj->api_lock);
1524   if (sample_rate == obj->sample_rate) {
1525     g_mutex_unlock (&obj->api_lock);
1526     return TRUE;
1527   }
1528 
1529   if (!obj->device_info->sta_model) {
1530     err = obj->asio_handle->setSampleRate (sample_rate);
1531     if (!err)
1532       obj->sample_rate = sample_rate;
1533 
1534     g_mutex_unlock (&obj->api_lock);
1535     return !err;
1536   }
1537 
1538   data.sample_rate = sample_rate;
1539   gst_asio_object_thread_add (obj,
1540       (GstAsioObjectThreadFunc) gst_asio_object_set_sample_rate_async, &data);
1541   g_mutex_unlock (&obj->api_lock);
1542 
1543   return !data.err;
1544 }
1545 
1546 static void
gst_asio_object_buffer_switch(GstAsioObject * self,glong index,ASIOBool process_now)1547 gst_asio_object_buffer_switch (GstAsioObject * self,
1548     glong index, ASIOBool process_now)
1549 {
1550   ASIOTime time_info;
1551   ASIOTime *our_time_info = nullptr;
1552   ASIOError err = 0;
1553 
1554   memset (&time_info, 0, sizeof (ASIOTime));
1555 
1556   err =
1557       self->asio_handle->getSamplePosition (&time_info.timeInfo.samplePosition,
1558       &time_info.timeInfo.systemTime);
1559   if (!err)
1560     our_time_info = &time_info;
1561 
1562   gst_asio_object_buffer_switch_time_info (self,
1563       our_time_info, index, process_now);
1564 }
1565 
1566 static void
gst_asio_object_sample_rate_changed(GstAsioObject * self,ASIOSampleRate rate)1567 gst_asio_object_sample_rate_changed (GstAsioObject * self, ASIOSampleRate rate)
1568 {
1569   GST_INFO_OBJECT (self, "SampleRate changed to %lf", rate);
1570 }
1571 
1572 static glong
gst_asio_object_messages(GstAsioObject * self,glong selector,glong value,gpointer message,gdouble * opt)1573 gst_asio_object_messages (GstAsioObject * self,
1574     glong selector, glong value, gpointer message, gdouble * opt)
1575 {
1576   GST_DEBUG_OBJECT (self, "ASIO message: %ld, %ld", selector, value);
1577 
1578   switch (selector) {
1579     case kAsioSelectorSupported:
1580       if (value == kAsioResetRequest || value == kAsioEngineVersion ||
1581           value == kAsioResyncRequest || value == kAsioLatenciesChanged ||
1582           value == kAsioSupportsTimeCode || value == kAsioSupportsInputMonitor)
1583         return 0;
1584       else if (value == kAsioSupportsTimeInfo)
1585         return 1;
1586       GST_WARNING_OBJECT (self, "Unsupported ASIO selector: %li", value);
1587       break;
1588     case kAsioBufferSizeChange:
1589       GST_WARNING_OBJECT (self,
1590           "Unsupported ASIO message: kAsioBufferSizeChange");
1591       break;
1592     case kAsioResetRequest:
1593       GST_WARNING_OBJECT (self, "Unsupported ASIO message: kAsioResetRequest");
1594       break;
1595     case kAsioResyncRequest:
1596       GST_WARNING_OBJECT (self, "Unsupported ASIO message: kAsioResyncRequest");
1597       break;
1598     case kAsioLatenciesChanged:
1599       GST_WARNING_OBJECT (self,
1600           "Unsupported ASIO message: kAsioLatenciesChanged");
1601       break;
1602     case kAsioEngineVersion:
1603       /* We target the ASIO v2 API, which includes ASIOOutputReady() */
1604       return 2;
1605     case kAsioSupportsTimeInfo:
1606       /* We use the new time info buffer switch callback */
1607       return 1;
1608     case kAsioSupportsTimeCode:
1609       /* We don't use the time code info right now */
1610       return 0;
1611     default:
1612       GST_WARNING_OBJECT (self, "Unsupported ASIO message: %li, %li", selector,
1613           value);
1614       break;
1615   }
1616 
1617   return 0;
1618 }
1619 
1620 #define PACK_ASIO_64(v) ((v).lo | ((guint64)((v).hi) << 32))
1621 
1622 static ASIOTime *
gst_asio_object_buffer_switch_time_info(GstAsioObject * self,ASIOTime * time_info,glong index,ASIOBool process_now)1623 gst_asio_object_buffer_switch_time_info (GstAsioObject * self,
1624     ASIOTime * time_info, glong index, ASIOBool process_now)
1625 {
1626   GList *iter;
1627 
1628   if (time_info) {
1629     guint64 pos;
1630     guint64 system_time;
1631 
1632     pos = PACK_ASIO_64 (time_info->timeInfo.samplePosition);
1633     system_time = PACK_ASIO_64 (time_info->timeInfo.systemTime);
1634 
1635     GST_TRACE_OBJECT (self, "Sample Position: %" G_GUINT64_FORMAT
1636         ", System Time: %" GST_TIME_FORMAT, pos, GST_TIME_ARGS (system_time));
1637   }
1638 
1639   g_mutex_lock (&self->api_lock);
1640   if (!self->src_client_callbacks && !self->sink_client_callbacks &&
1641       !self->loopback_client_callbacks) {
1642     GST_WARNING_OBJECT (self, "No installed client callback");
1643     goto out;
1644   }
1645 
1646   for (iter = self->src_client_callbacks; iter;) {
1647     GstAsioObjectCallbacksPrivate *cb =
1648         (GstAsioObjectCallbacksPrivate *) iter->data;
1649     gboolean ret;
1650 
1651     ret = cb->callbacks.buffer_switch (self, index, self->buffer_infos,
1652         self->num_allocated_buffers, self->input_channel_infos,
1653         self->output_channel_infos, self->sample_rate,
1654         self->selected_buffer_size, time_info, cb->callbacks.user_data);
1655     if (!ret) {
1656       GST_INFO_OBJECT (self, "Remove callback for id %" G_GUINT64_FORMAT,
1657           cb->callback_id);
1658       GList *to_remove = iter;
1659       iter = g_list_next (iter);
1660 
1661       g_free (to_remove->data);
1662       g_list_free (to_remove);
1663     }
1664 
1665     iter = g_list_next (iter);
1666   }
1667 
1668   for (iter = self->sink_client_callbacks; iter;) {
1669     GstAsioObjectCallbacksPrivate *cb =
1670         (GstAsioObjectCallbacksPrivate *) iter->data;
1671     gboolean ret;
1672 
1673     ret = cb->callbacks.buffer_switch (self, index, self->buffer_infos,
1674         self->num_allocated_buffers, self->input_channel_infos,
1675         self->output_channel_infos, self->sample_rate,
1676         self->selected_buffer_size, time_info, cb->callbacks.user_data);
1677     if (!ret) {
1678       GST_INFO_OBJECT (self, "Remove callback for id %" G_GUINT64_FORMAT,
1679           cb->callback_id);
1680       GList *to_remove = iter;
1681       iter = g_list_next (iter);
1682 
1683       g_free (to_remove->data);
1684       g_list_free (to_remove);
1685     }
1686 
1687     iter = g_list_next (iter);
1688   }
1689 
1690   for (iter = self->loopback_client_callbacks; iter;) {
1691     GstAsioObjectCallbacksPrivate *cb =
1692         (GstAsioObjectCallbacksPrivate *) iter->data;
1693     gboolean ret;
1694 
1695     ret = cb->callbacks.buffer_switch (self, index, self->buffer_infos,
1696         self->num_allocated_buffers, self->input_channel_infos,
1697         self->output_channel_infos, self->sample_rate,
1698         self->selected_buffer_size, time_info, cb->callbacks.user_data);
1699     if (!ret) {
1700       GST_INFO_OBJECT (self, "Remove callback for id %" G_GUINT64_FORMAT,
1701           cb->callback_id);
1702       GList *to_remove = iter;
1703       iter = g_list_next (iter);
1704 
1705       g_free (to_remove->data);
1706       g_list_free (to_remove);
1707     }
1708 
1709     iter = g_list_next (iter);
1710   }
1711 
1712   self->asio_handle->outputReady ();
1713 
1714 out:
1715   g_mutex_unlock (&self->api_lock);
1716 
1717   return nullptr;
1718 }
1719 
1720 static void
gst_asio_object_start_async(GstAsioObject * self,ASIOError * rst)1721 gst_asio_object_start_async (GstAsioObject * self, ASIOError * rst)
1722 {
1723   *rst = self->asio_handle->start ();
1724 }
1725 
1726 gboolean
gst_asio_object_start(GstAsioObject * obj)1727 gst_asio_object_start (GstAsioObject * obj)
1728 {
1729   ASIOError ret;
1730 
1731   g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
1732 
1733   g_mutex_lock (&obj->api_lock);
1734   if (obj->state > GST_ASIO_OBJECT_STATE_PREPARED) {
1735     GST_DEBUG_OBJECT (obj, "We are running already");
1736     g_mutex_unlock (&obj->api_lock);
1737 
1738     return TRUE;
1739   } else if (obj->state < GST_ASIO_OBJECT_STATE_PREPARED) {
1740     GST_ERROR_OBJECT (obj, "We are not prepared");
1741     g_mutex_unlock (&obj->api_lock);
1742 
1743     return FALSE;
1744   }
1745 
1746   /* Then start */
1747   if (!obj->device_info->sta_model) {
1748     ret = obj->asio_handle->start ();
1749   } else {
1750     gst_asio_object_thread_add (obj,
1751         (GstAsioObjectThreadFunc) gst_asio_object_start_async, &ret);
1752   }
1753 
1754   if (ret != 0) {
1755     GST_ERROR_OBJECT (obj, "Failed to start object");
1756     g_mutex_unlock (&obj->api_lock);
1757 
1758     return FALSE;
1759   }
1760 
1761   obj->state = GST_ASIO_OBJECT_STATE_RUNNING;
1762   g_mutex_unlock (&obj->api_lock);
1763 
1764   return TRUE;
1765 }
1766 
1767 gboolean
gst_asio_object_install_callback(GstAsioObject * obj,GstAsioDeviceClassType type,GstAsioObjectCallbacks * callbacks,guint64 * callback_id)1768 gst_asio_object_install_callback (GstAsioObject * obj,
1769     GstAsioDeviceClassType type,
1770     GstAsioObjectCallbacks * callbacks, guint64 * callback_id)
1771 {
1772   GstAsioObjectCallbacksPrivate *cb;
1773 
1774   g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
1775   g_return_val_if_fail (callbacks != nullptr, FALSE);
1776   g_return_val_if_fail (callback_id != nullptr, FALSE);
1777 
1778   g_mutex_lock (&obj->api_lock);
1779   cb = g_new0 (GstAsioObjectCallbacksPrivate, 1);
1780   cb->callbacks = *callbacks;
1781   cb->callback_id = obj->next_callback_id;
1782 
1783   switch (type) {
1784     case GST_ASIO_DEVICE_CLASS_CAPTURE:
1785       obj->src_client_callbacks = g_list_append (obj->src_client_callbacks, cb);
1786       break;
1787     case GST_ASIO_DEVICE_CLASS_RENDER:
1788       obj->sink_client_callbacks =
1789           g_list_append (obj->sink_client_callbacks, cb);
1790       break;
1791     case GST_ASIO_DEVICE_CLASS_LOOPBACK_CAPTURE:
1792       obj->loopback_client_callbacks =
1793           g_list_append (obj->loopback_client_callbacks, cb);
1794       break;
1795     default:
1796       g_assert_not_reached ();
1797       g_free (cb);
1798       return FALSE;
1799   }
1800 
1801   *callback_id = cb->callback_id;
1802   g_mutex_unlock (&obj->api_lock);
1803 
1804   return TRUE;
1805 }
1806 
1807 void
gst_asio_object_uninstall_callback(GstAsioObject * obj,guint64 callback_id)1808 gst_asio_object_uninstall_callback (GstAsioObject * obj, guint64 callback_id)
1809 {
1810   GList *iter;
1811 
1812   g_return_if_fail (GST_IS_ASIO_OBJECT (obj));
1813 
1814   g_mutex_lock (&obj->api_lock);
1815 
1816   GST_DEBUG_OBJECT (obj, "Removing callback id %" G_GUINT64_FORMAT,
1817       callback_id);
1818 
1819   for (iter = obj->src_client_callbacks; iter; iter = g_list_next (iter)) {
1820     GstAsioObjectCallbacksPrivate *cb =
1821         (GstAsioObjectCallbacksPrivate *) iter->data;
1822 
1823     if (cb->callback_id != callback_id)
1824       continue;
1825 
1826     GST_DEBUG_OBJECT (obj, "Found src callback for id %" G_GUINT64_FORMAT,
1827         callback_id);
1828 
1829     obj->src_client_callbacks =
1830         g_list_remove_link (obj->src_client_callbacks, iter);
1831     g_free (iter->data);
1832     g_list_free (iter);
1833     g_mutex_unlock (&obj->api_lock);
1834 
1835     return;
1836   }
1837 
1838   for (iter = obj->sink_client_callbacks; iter; iter = g_list_next (iter)) {
1839     GstAsioObjectCallbacksPrivate *cb =
1840         (GstAsioObjectCallbacksPrivate *) iter->data;
1841 
1842     if (cb->callback_id != callback_id)
1843       continue;
1844 
1845     GST_DEBUG_OBJECT (obj, "Found sink callback for id %" G_GUINT64_FORMAT,
1846         callback_id);
1847 
1848     obj->sink_client_callbacks =
1849         g_list_remove_link (obj->sink_client_callbacks, iter);
1850     g_free (iter->data);
1851     g_list_free (iter);
1852     g_mutex_unlock (&obj->api_lock);
1853 
1854     return;
1855   }
1856 
1857   for (iter = obj->loopback_client_callbacks; iter; iter = g_list_next (iter)) {
1858     GstAsioObjectCallbacksPrivate *cb =
1859         (GstAsioObjectCallbacksPrivate *) iter->data;
1860 
1861     if (cb->callback_id != callback_id)
1862       continue;
1863 
1864     GST_DEBUG_OBJECT (obj, "Found loopback callback for id %" G_GUINT64_FORMAT,
1865         callback_id);
1866 
1867     obj->loopback_client_callbacks =
1868         g_list_remove_link (obj->loopback_client_callbacks, iter);
1869     g_free (iter->data);
1870     g_list_free (iter);
1871     break;
1872   }
1873 
1874   g_mutex_unlock (&obj->api_lock);
1875 }
1876