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