Introduce Steinberg ASIO (Audio Streaming Input/Output) plugin
authorSeungha Yang <seungha@centricular.com>
Mon, 7 Jun 2021 16:40:34 +0000 (01:40 +0900)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Mon, 26 Jul 2021 14:58:16 +0000 (14:58 +0000)
Adds a new plugin for ASIO devices.

Although there is a standard low-level audio API, WASAPI, on Windows,
ASIO is still being broadly used for audio devices which are aiming to
professional use case. In case of such devices, ASIO API might be able
to show better quality and latency performance depending on manufacturer's
driver implementation.

In order to build this plugin, user should provide path to
ASIO SDK as a build option, "asio-sdk-path".

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2309>

16 files changed:
meson_options.txt
sys/asio/gstasiodeviceprovider.cpp [new file with mode: 0644]
sys/asio/gstasiodeviceprovider.h [new file with mode: 0644]
sys/asio/gstasioobject.cpp [new file with mode: 0644]
sys/asio/gstasioobject.h [new file with mode: 0644]
sys/asio/gstasioringbuffer.cpp [new file with mode: 0644]
sys/asio/gstasioringbuffer.h [new file with mode: 0644]
sys/asio/gstasiosink.cpp [new file with mode: 0644]
sys/asio/gstasiosink.h [new file with mode: 0644]
sys/asio/gstasiosrc.cpp [new file with mode: 0644]
sys/asio/gstasiosrc.h [new file with mode: 0644]
sys/asio/gstasioutils.cpp [new file with mode: 0644]
sys/asio/gstasioutils.h [new file with mode: 0644]
sys/asio/meson.build [new file with mode: 0644]
sys/asio/plugin.c [new file with mode: 0644]
sys/meson.build

index 4a97780..c24b3b3 100644 (file)
@@ -81,6 +81,8 @@ option('aom', type : 'feature', value : 'auto', description : 'AOM AV1 video cod
 option('avtp', type : 'feature', value : 'auto', description : 'Audio/Video Transport Protocol (AVTP) plugin')
 option('androidmedia', type : 'feature', value : 'auto', description : 'Video capture and codec plugins for Android')
 option('applemedia', type : 'feature', value : 'auto', description : 'Video capture and codec access plugins for macOS and iOS')
+option('asio', type : 'feature', value : 'auto', description : 'Steinberg Audio Streaming Input Output (ASIO) plugin')
+option('asio-sdk-path', type : 'string', value : '', description : 'Full path to Steinberg Audio Streaming Input Output (ASIO) SDK')
 option('assrender', type : 'feature', value : 'auto', description : 'ASS/SSA subtitle renderer plugin')
 option('bluez', type : 'feature', value : 'auto', description : 'Bluetooth audio A2DP/AVDTP sink, AVDTP source plugin')
 option('bs2b', type : 'feature', value : 'auto', description : 'Bauer stereophonic-to-binaural audio plugin')
diff --git a/sys/asio/gstasiodeviceprovider.cpp b/sys/asio/gstasiodeviceprovider.cpp
new file mode 100644 (file)
index 0000000..4d1e9b0
--- /dev/null
@@ -0,0 +1,273 @@
+/* GStreamer
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstasiodeviceprovider.h"
+#include "gstasioutils.h"
+#include "gstasioobject.h"
+#include <atlconv.h>
+
+enum
+{
+  PROP_0,
+  PROP_DEVICE_CLSID,
+};
+
+struct _GstAsioDevice
+{
+  GstDevice parent;
+
+  gchar *device_clsid;
+  const gchar *factory_name;
+};
+
+G_DEFINE_TYPE (GstAsioDevice, gst_asio_device, GST_TYPE_DEVICE);
+
+static void gst_asio_device_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec);
+static void gst_asio_device_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_asio_device_finalize (GObject * object);
+static GstElement *gst_asio_device_create_element (GstDevice * device,
+    const gchar * name);
+
+static void
+gst_asio_device_class_init (GstAsioDeviceClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
+
+  dev_class->create_element = gst_asio_device_create_element;
+
+  gobject_class->get_property = gst_asio_device_get_property;
+  gobject_class->set_property = gst_asio_device_set_property;
+  gobject_class->finalize = gst_asio_device_finalize;
+
+  g_object_class_install_property (gobject_class, PROP_DEVICE_CLSID,
+      g_param_spec_string ("device-clsid", "Device CLSID",
+          "ASIO device CLSID as string including curly brackets", NULL,
+          (GParamFlags) (G_PARAM_READWRITE |
+              G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)));
+}
+
+static void
+gst_asio_device_init (GstAsioDevice * self)
+{
+}
+
+static void
+gst_asio_device_finalize (GObject * object)
+{
+  GstAsioDevice *self = GST_ASIO_DEVICE (object);
+
+  g_free (self->device_clsid);
+
+  G_OBJECT_CLASS (gst_asio_device_parent_class)->finalize (object);
+}
+
+static GstElement *
+gst_asio_device_create_element (GstDevice * device, const gchar * name)
+{
+  GstAsioDevice *self = GST_ASIO_DEVICE (device);
+  GstElement *elem;
+
+  elem = gst_element_factory_make (self->factory_name, name);
+
+  g_object_set (elem, "device-clsid", self->device_clsid, NULL);
+
+  return elem;
+}
+
+static void
+gst_asio_device_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstAsioDevice *self = GST_ASIO_DEVICE (object);
+
+  switch (prop_id) {
+    case PROP_DEVICE_CLSID:
+      g_value_set_string (value, self->device_clsid);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_asio_device_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstAsioDevice *self = GST_ASIO_DEVICE (object);
+
+  switch (prop_id) {
+    case PROP_DEVICE_CLSID:
+      g_free (self->device_clsid);
+      self->device_clsid = g_value_dup_string (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+struct _GstAsioDeviceProvider
+{
+  GstDeviceProvider parent;
+};
+
+G_DEFINE_TYPE (GstAsioDeviceProvider, gst_asio_device_provider,
+    GST_TYPE_DEVICE_PROVIDER);
+
+static GList *gst_asio_device_provider_probe (GstDeviceProvider * provider);
+
+static void
+gst_asio_device_provider_class_init (GstAsioDeviceProviderClass * klass)
+{
+  GstDeviceProviderClass *provider_class = GST_DEVICE_PROVIDER_CLASS (klass);
+
+  provider_class->probe = GST_DEBUG_FUNCPTR (gst_asio_device_provider_probe);
+
+  gst_device_provider_class_set_static_metadata (provider_class,
+      "ASIO Device Provider",
+      "Source/Sink/Audio", "List ASIO source and sink devices",
+      "Seungha Yang <seungha@centricular.com>");
+}
+
+static void
+gst_asio_device_provider_init (GstAsioDeviceProvider * provider)
+{
+}
+
+static void
+gst_asio_device_provider_probe_internal (GstAsioDeviceProvider * self,
+    gboolean is_src, GList * asio_device_list, GList ** devices)
+{
+  const gchar *device_class, *factory_name;
+  GList *iter;
+
+  USES_CONVERSION;
+
+  if (is_src) {
+    device_class = "Audio/Source";
+    factory_name = "asiosrc";
+  } else {
+    device_class = "Audio/Sink";
+    factory_name = "asiosink";
+  }
+
+  for (iter = asio_device_list; iter; iter = g_list_next (iter)) {
+    GstDevice *device;
+    GstAsioDeviceInfo *info = (GstAsioDeviceInfo *) iter->data;
+    GstAsioObject *obj;
+    GstCaps *caps = nullptr;
+    GstStructure *props = nullptr;
+    long max_in_ch = 0;
+    long max_out_ch = 0;
+    HRESULT hr;
+    LPOLESTR clsid_str = nullptr;
+    glong min_buf_size = 0;
+    glong max_buf_size = 0;
+    glong preferred_buf_size = 0;
+    glong buf_size_granularity = 0;
+
+    obj = gst_asio_object_new (info, FALSE);
+    if (!obj)
+      continue;
+
+    if (!gst_asio_object_get_max_num_channels (obj, &max_in_ch, &max_out_ch))
+      goto done;
+
+    if (is_src && max_in_ch <= 0)
+      goto done;
+    else if (!is_src && max_out_ch <= 0)
+      goto done;
+
+    if (is_src) {
+      caps = gst_asio_object_get_caps (obj,
+          GST_ASIO_DEVICE_CLASS_CAPTURE, 1, max_in_ch);
+    } else {
+      caps = gst_asio_object_get_caps (obj,
+          GST_ASIO_DEVICE_CLASS_RENDER, 1, max_out_ch);
+    }
+    if (!caps)
+      goto done;
+
+    hr = StringFromIID (info->clsid, &clsid_str);
+    if (FAILED (hr))
+      goto done;
+
+    if (!gst_asio_object_get_buffer_size (obj, &min_buf_size, &max_buf_size,
+            &preferred_buf_size, &buf_size_granularity))
+      goto done;
+
+    props = gst_structure_new ("asio-proplist",
+        "device.api", G_TYPE_STRING, "asio",
+        "device.clsid", G_TYPE_STRING, OLE2A (clsid_str),
+        "asio.device.description", G_TYPE_STRING, info->driver_desc,
+        "asio.device.min-buf-size", G_TYPE_LONG, min_buf_size,
+        "asio.device.max-buf-size", G_TYPE_LONG, max_buf_size,
+        "asio.device.preferred-buf-size", G_TYPE_LONG, preferred_buf_size,
+        "asio.device.buf-size-granularity", G_TYPE_LONG, buf_size_granularity,
+        nullptr);
+
+    device = (GstDevice *) g_object_new (GST_TYPE_ASIO_DEVICE,
+        "device-clsid", OLE2A (clsid_str),
+        "display-name", info->driver_desc, "caps", caps,
+        "device-class", device_class, "properties", props, nullptr);
+    GST_ASIO_DEVICE (device)->factory_name = factory_name;
+
+    *devices = g_list_append (*devices, device);
+
+  done:
+    gst_clear_caps (&caps);
+    gst_clear_object (&obj);
+    if (props)
+      gst_structure_free (props);
+  }
+
+  return;
+}
+
+static GList *
+gst_asio_device_provider_probe (GstDeviceProvider * provider)
+{
+  GstAsioDeviceProvider *self = GST_ASIO_DEVICE_PROVIDER (provider);
+  GList *devices = nullptr;
+  guint num_device;
+  GList *asio_device_list = nullptr;
+
+  num_device = gst_asio_enum (&asio_device_list);
+
+  if (num_device == 0)
+    return nullptr;
+
+  gst_asio_device_provider_probe_internal (self,
+      TRUE, asio_device_list, &devices);
+  gst_asio_device_provider_probe_internal (self,
+      FALSE, asio_device_list, &devices);
+
+  g_list_free_full (asio_device_list,
+      (GDestroyNotify) gst_asio_device_info_free);
+
+  return devices;
+}
diff --git a/sys/asio/gstasiodeviceprovider.h b/sys/asio/gstasiodeviceprovider.h
new file mode 100644 (file)
index 0000000..24126e8
--- /dev/null
@@ -0,0 +1,37 @@
+/* GStreamer
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_ASIO_DEVICE_PROVIDER_H__
+#define __GST_ASIO_DEVICE_PROVIDER_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_ASIO_DEVICE (gst_asio_device_get_type())
+#define GST_TYPE_ASIO_DEVICE_PROVIDER (gst_asio_device_provider_get_type())
+
+G_DECLARE_FINAL_TYPE (GstAsioDevice, gst_asio_device,
+    GST, ASIO_DEVICE, GstDevice);
+G_DECLARE_FINAL_TYPE (GstAsioDeviceProvider, gst_asio_device_provider,
+    GST, ASIO_DEVICE_PROVIDER, GstDeviceProvider);
+
+G_END_DECLS
+
+#endif /* __GST_ASIO_DEVICE_PROVIDER_H__ */
\ No newline at end of file
diff --git a/sys/asio/gstasioobject.cpp b/sys/asio/gstasioobject.cpp
new file mode 100644 (file)
index 0000000..675ffc8
--- /dev/null
@@ -0,0 +1,1875 @@
+/* GStreamer
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstasioobject.h"
+#include <string.h>
+#include <avrt.h>
+#include <string>
+#include <functional>
+#include <vector>
+#include <mutex>
+#include <iasiodrv.h>
+
+GST_DEBUG_CATEGORY_STATIC (gst_asio_object_debug);
+#define GST_CAT_DEFAULT gst_asio_object_debug
+
+/* List of GstAsioObject */
+static GList *asio_object_list = nullptr;
+
+/* *INDENT-OFF* */
+/* Protect asio_object_list and other global values */
+std::mutex global_lock;
+
+/* Protect callback slots */
+std::mutex slot_lock;
+/* *INDENT-ON* */
+
+static void gst_asio_object_buffer_switch (GstAsioObject * self,
+    glong index, ASIOBool process_now);
+static void gst_asio_object_sample_rate_changed (GstAsioObject * self,
+    ASIOSampleRate rate);
+static glong gst_asio_object_messages (GstAsioObject * self, glong selector,
+    glong value, gpointer message, gdouble * opt);
+static ASIOTime *gst_asio_object_buffer_switch_time_info (GstAsioObject * self,
+    ASIOTime * time_info, glong index, ASIOBool process_now);
+
+/* *INDENT-OFF* */
+/* Object to delegate ASIO callbacks to dedicated GstAsioObject */
+class GstAsioCallbacks
+{
+public:
+  GstAsioCallbacks (GstAsioObject * object)
+  {
+    g_weak_ref_init (&object_, object);
+  }
+
+  virtual ~GstAsioCallbacks ()
+  {
+    g_weak_ref_clear (&object_);
+  }
+
+  void BufferSwitch (glong index, ASIOBool process_now)
+  {
+    GstAsioObject *obj = (GstAsioObject *) g_weak_ref_get (&object_);
+    if (!obj)
+      return;
+
+    gst_asio_object_buffer_switch (obj, index, process_now);
+    gst_object_unref (obj);
+  }
+
+  void SampleRateChanged (ASIOSampleRate rate)
+  {
+    GstAsioObject *obj = (GstAsioObject *) g_weak_ref_get (&object_);
+    if (!obj)
+      return;
+
+    gst_asio_object_sample_rate_changed (obj, rate);
+    gst_object_unref (obj);
+  }
+
+  glong Messages (glong selector, glong value, gpointer message, gdouble *opt)
+  {
+    GstAsioObject *obj = (GstAsioObject *) g_weak_ref_get (&object_);
+    if (!obj)
+      return 0;
+
+    glong ret = gst_asio_object_messages (obj, selector, value, message, opt);
+    gst_object_unref (obj);
+
+    return ret;
+  }
+
+  ASIOTime * BufferSwitchTimeInfo (ASIOTime * time_info,
+    glong index, ASIOBool process_now)
+  {
+    GstAsioObject *obj = (GstAsioObject *) g_weak_ref_get (&object_);
+    if (!obj)
+      return nullptr;
+
+    ASIOTime * ret = gst_asio_object_buffer_switch_time_info (obj,
+        time_info, index, process_now);
+    gst_object_unref (obj);
+
+    return ret;
+  }
+
+private:
+  GWeakRef object_;
+};
+
+template <int instance_id>
+class GstAsioCallbacksSlot
+{
+public:
+  static void
+  BufferSwitchStatic(glong index, ASIOBool process_now)
+  {
+    buffer_switch(index, process_now);
+  }
+
+  static void
+  SampleRateChangedStatic (ASIOSampleRate rate)
+  {
+    sample_rate_changed(rate);
+  }
+
+  static glong
+  MessagesStatic(glong selector, glong value, gpointer message, gdouble *opt)
+  {
+    return messages(selector, value, message, opt);
+  }
+
+  static ASIOTime *
+  BufferSwitchTimeInfoStatic(ASIOTime * time_info, glong index,
+      ASIOBool process_now)
+  {
+    return buffer_switch_time_info(time_info, index, process_now);
+  }
+
+  static std::function<void(glong, ASIOBool)> buffer_switch;
+  static std::function<void(ASIOSampleRate)> sample_rate_changed;
+  static std::function<glong(glong, glong, gpointer, gdouble *)> messages;
+  static std::function<ASIOTime *(ASIOTime *, glong, ASIOBool)> buffer_switch_time_info;
+
+  static bool bound;
+
+  static void Init ()
+  {
+    buffer_switch = nullptr;
+    sample_rate_changed = nullptr;
+    messages = nullptr;
+    buffer_switch_time_info = nullptr;
+    bound = false;
+  }
+
+  static bool IsBound ()
+  {
+    return bound;
+  }
+
+  static void Bind (GstAsioCallbacks * cb, ASIOCallbacks * driver_cb)
+  {
+    buffer_switch = std::bind(&GstAsioCallbacks::BufferSwitch, cb,
+      std::placeholders::_1, std::placeholders::_2);
+    sample_rate_changed = std::bind(&GstAsioCallbacks::SampleRateChanged, cb,
+      std::placeholders::_1);
+    messages = std::bind(&GstAsioCallbacks::Messages, cb,
+      std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
+      std::placeholders::_4);
+    buffer_switch_time_info = std::bind(&GstAsioCallbacks::BufferSwitchTimeInfo,
+      cb, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
+
+    driver_cb->bufferSwitch = BufferSwitchStatic;
+    driver_cb->sampleRateDidChange = SampleRateChangedStatic;
+    driver_cb->asioMessage = MessagesStatic;
+    driver_cb->bufferSwitchTimeInfo = BufferSwitchTimeInfoStatic;
+
+    bound = true;
+  }
+};
+
+template <int instance_id>
+std::function<void(glong, ASIOBool)> GstAsioCallbacksSlot<instance_id>::buffer_switch;
+template <int instance_id>
+std::function<void(ASIOSampleRate)> GstAsioCallbacksSlot<instance_id>::sample_rate_changed;
+template <int instance_id>
+std::function<glong(glong, glong, gpointer, gdouble *)> GstAsioCallbacksSlot<instance_id>::messages;
+template <int instance_id>
+std::function<ASIOTime *(ASIOTime *, glong, ASIOBool)> GstAsioCallbacksSlot<instance_id>::buffer_switch_time_info;
+template <int instance_id>
+bool GstAsioCallbacksSlot<instance_id>::bound;
+
+/* XXX: Create global slot objects,
+ * because ASIO callback doesn't support user data, hum.... */
+GstAsioCallbacksSlot<0> cb_slot_0;
+GstAsioCallbacksSlot<1> cb_slot_1;
+GstAsioCallbacksSlot<2> cb_slot_2;
+GstAsioCallbacksSlot<3> cb_slot_3;
+GstAsioCallbacksSlot<4> cb_slot_4;
+GstAsioCallbacksSlot<5> cb_slot_5;
+GstAsioCallbacksSlot<6> cb_slot_6;
+GstAsioCallbacksSlot<7> cb_slot_7;
+
+/* *INDENT-ON* */
+
+typedef struct
+{
+  GstAsioObjectCallbacks callbacks;
+  guint64 callback_id;
+} GstAsioObjectCallbacksPrivate;
+
+enum
+{
+  PROP_0,
+  PROP_DEVICE_INFO,
+};
+
+typedef enum
+{
+  GST_ASIO_OBJECT_STATE_LOADED,
+  GST_ASIO_OBJECT_STATE_INITIALIZED,
+  GST_ASIO_OBJECT_STATE_PREPARED,
+  GST_ASIO_OBJECT_STATE_RUNNING,
+} GstAsioObjectState;
+
+/* Protect singletone object */
+struct _GstAsioObject
+{
+  GstObject parent;
+
+  GstAsioDeviceInfo *device_info;
+
+  GstAsioObjectState state;
+
+  IASIO *asio_handle;
+
+  GThread *thread;
+  GMutex lock;
+  GCond cond;
+  GMainContext *context;
+  GMainLoop *loop;
+
+  GMutex thread_lock;
+  GCond thread_cond;
+
+  GMutex api_lock;
+
+  /* called after init() done */
+  glong max_num_input_channels;
+  glong max_num_output_channels;
+
+  glong min_buffer_size;
+  glong max_buffer_size;
+  glong preferred_buffer_size;
+  glong buffer_size_granularity;
+
+  glong selected_buffer_size;
+
+  /* List of supported sample rate */
+  GArray *supported_sample_rates;
+
+  /* List of ASIOChannelInfo */
+  ASIOChannelInfo *input_channel_infos;
+  ASIOChannelInfo *output_channel_infos;
+
+  /* Selected sample rate */
+  ASIOSampleRate sample_rate;
+
+  /* Input/Output buffer infors */
+  ASIOBufferInfo *buffer_infos;
+
+  /* Store requested channel before createbuffer */
+  gboolean *input_channel_requested;
+  gboolean *output_channel_requested;
+
+  glong num_requested_input_channels;
+  glong num_requested_output_channels;
+  guint num_allocated_buffers;
+
+  GList *src_client_callbacks;
+  GList *sink_client_callbacks;
+  GList *loopback_client_callbacks;
+  guint64 next_callback_id;
+
+  GstAsioCallbacks *callbacks;
+  ASIOCallbacks driver_callbacks;
+  int slot_id;
+
+  gboolean occupy_all_channels;
+};
+
+static void gst_asio_object_constructed (GObject * object);
+static void gst_asio_object_finalize (GObject * object);
+static void gst_asio_object_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+
+static gpointer gst_asio_object_thread_func (GstAsioObject * self);
+
+#define gst_asio_object_parent_class parent_class
+G_DEFINE_TYPE (GstAsioObject, gst_asio_object, GST_TYPE_OBJECT);
+
+static void
+gst_asio_object_class_init (GstAsioObjectClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->constructed = gst_asio_object_constructed;
+  gobject_class->finalize = gst_asio_object_finalize;
+  gobject_class->set_property = gst_asio_object_set_property;
+
+  g_object_class_install_property (gobject_class, PROP_DEVICE_INFO,
+      g_param_spec_pointer ("device-info", "Device Info",
+          "A pointer to GstAsioDeviceInfo struct",
+          (GParamFlags) (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+              G_PARAM_STATIC_STRINGS)));
+
+  GST_DEBUG_CATEGORY_INIT (gst_asio_object_debug,
+      "asioobject", 0, "asioobject");
+}
+
+static void
+gst_asio_object_init (GstAsioObject * self)
+{
+  g_mutex_init (&self->lock);
+  g_cond_init (&self->cond);
+
+  g_mutex_init (&self->thread_lock);
+  g_cond_init (&self->thread_cond);
+
+  g_mutex_init (&self->api_lock);
+
+  self->supported_sample_rates = g_array_new (FALSE,
+      FALSE, sizeof (ASIOSampleRate));
+
+  self->slot_id = -1;
+}
+
+static void
+gst_asio_object_constructed (GObject * object)
+{
+  GstAsioObject *self = GST_ASIO_OBJECT (object);
+
+  if (!self->device_info) {
+    GST_ERROR_OBJECT (self, "Device info was not configured");
+    return;
+  }
+
+  self->context = g_main_context_new ();
+  self->loop = g_main_loop_new (self->context, FALSE);
+
+  g_mutex_lock (&self->lock);
+  self->thread = g_thread_new ("GstAsioObject",
+      (GThreadFunc) gst_asio_object_thread_func, self);
+  while (!g_main_loop_is_running (self->loop))
+    g_cond_wait (&self->cond, &self->lock);
+  g_mutex_unlock (&self->lock);
+}
+
+static void
+gst_asio_object_finalize (GObject * object)
+{
+  GstAsioObject *self = GST_ASIO_OBJECT (object);
+
+  if (self->loop) {
+    g_main_loop_quit (self->loop);
+    g_thread_join (self->thread);
+    g_main_loop_unref (self->loop);
+    g_main_context_unref (self->context);
+  }
+
+  g_mutex_clear (&self->lock);
+  g_cond_clear (&self->cond);
+
+  g_mutex_clear (&self->thread_lock);
+  g_cond_clear (&self->thread_cond);
+
+  g_mutex_clear (&self->api_lock);
+
+  g_array_unref (self->supported_sample_rates);
+
+  gst_asio_device_info_free (self->device_info);
+  g_free (self->input_channel_infos);
+  g_free (self->output_channel_infos);
+  g_free (self->input_channel_requested);
+  g_free (self->output_channel_requested);
+
+  if (self->src_client_callbacks)
+    g_list_free_full (self->src_client_callbacks, (GDestroyNotify) g_free);
+  if (self->sink_client_callbacks)
+    g_list_free_full (self->sink_client_callbacks, (GDestroyNotify) g_free);
+  if (self->loopback_client_callbacks)
+    g_list_free_full (self->loopback_client_callbacks, (GDestroyNotify) g_free);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_asio_object_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstAsioObject *self = GST_ASIO_OBJECT (object);
+
+  switch (prop_id) {
+    case PROP_DEVICE_INFO:
+      g_clear_pointer (&self->device_info, gst_asio_device_info_free);
+      self->device_info = gst_asio_device_info_copy ((GstAsioDeviceInfo *)
+          g_value_get_pointer (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static HWND
+gst_asio_object_create_internal_hwnd (GstAsioObject * self)
+{
+  WNDCLASSEXW wc;
+  ATOM atom = 0;
+  HINSTANCE hinstance = GetModuleHandle (NULL);
+
+  atom = GetClassInfoExW (hinstance, L"GstAsioInternalWindow", &wc);
+  if (atom == 0) {
+    GST_LOG_OBJECT (self, "Register internal window class");
+    ZeroMemory (&wc, sizeof (WNDCLASSEX));
+
+    wc.cbSize = sizeof (WNDCLASSEX);
+    wc.lpfnWndProc = DefWindowProc;
+    wc.hInstance = GetModuleHandle (nullptr);
+    wc.style = CS_OWNDC;
+    wc.lpszClassName = L"GstAsioInternalWindow";
+
+    atom = RegisterClassExW (&wc);
+
+    if (atom == 0) {
+      GST_ERROR_OBJECT (self, "Failed to register window class 0x%x",
+          (unsigned int) GetLastError ());
+      return nullptr;
+    }
+  }
+
+  return CreateWindowExW (0, L"GstAsioInternalWindow", L"GstAsioInternal",
+      WS_POPUP, 0, 0, 1, 1, nullptr, nullptr, GetModuleHandle (nullptr),
+      nullptr);
+}
+
+static gboolean
+hwnd_msg_cb (GIOChannel * source, GIOCondition condition, gpointer data)
+{
+  MSG msg;
+
+  if (!PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
+    return G_SOURCE_CONTINUE;
+
+  TranslateMessage (&msg);
+  DispatchMessage (&msg);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+gst_asio_object_main_loop_running_cb (GstAsioObject * self)
+{
+  GST_INFO_OBJECT (self, "Main loop running now");
+
+  g_mutex_lock (&self->lock);
+  g_cond_signal (&self->cond);
+  g_mutex_unlock (&self->lock);
+
+  return G_SOURCE_REMOVE;
+}
+
+static gboolean
+gst_asio_object_bind_callbacks (GstAsioObject * self)
+{
+  std::lock_guard < std::mutex > lk (slot_lock);
+  gboolean ret = TRUE;
+
+  if (!cb_slot_0.IsBound ()) {
+    cb_slot_0.Bind (self->callbacks, &self->driver_callbacks);
+    self->slot_id = 0;
+  } else if (!cb_slot_1.IsBound ()) {
+    cb_slot_1.Bind (self->callbacks, &self->driver_callbacks);
+    self->slot_id = 1;
+  } else if (!cb_slot_2.IsBound ()) {
+    cb_slot_2.Bind (self->callbacks, &self->driver_callbacks);
+    self->slot_id = 2;
+  } else if (!cb_slot_3.IsBound ()) {
+    cb_slot_3.Bind (self->callbacks, &self->driver_callbacks);
+    self->slot_id = 3;
+  } else if (!cb_slot_4.IsBound ()) {
+    cb_slot_4.Bind (self->callbacks, &self->driver_callbacks);
+    self->slot_id = 4;
+  } else if (!cb_slot_5.IsBound ()) {
+    cb_slot_5.Bind (self->callbacks, &self->driver_callbacks);
+    self->slot_id = 5;
+  } else if (!cb_slot_6.IsBound ()) {
+    cb_slot_6.Bind (self->callbacks, &self->driver_callbacks);
+    self->slot_id = 6;
+  } else if (!cb_slot_7.IsBound ()) {
+    cb_slot_7.Bind (self->callbacks, &self->driver_callbacks);
+    self->slot_id = 7;
+  } else {
+    self->slot_id = -1;
+    ret = FALSE;
+  }
+
+  return ret;
+}
+
+static void
+gst_asio_object_unbind_callbacks (GstAsioObject * self)
+{
+  std::lock_guard < std::mutex > lk (slot_lock);
+
+  if (!self->callbacks || self->slot_id < 0)
+    return;
+
+  switch (self->slot_id) {
+    case 0:
+      cb_slot_0.Init ();
+      break;
+    case 1:
+      cb_slot_1.Init ();
+      break;
+    case 2:
+      cb_slot_2.Init ();
+      break;
+    case 3:
+      cb_slot_3.Init ();
+      break;
+    case 4:
+      cb_slot_4.Init ();
+      break;
+    case 5:
+      cb_slot_5.Init ();
+      break;
+    case 6:
+      cb_slot_6.Init ();
+      break;
+    case 7:
+      cb_slot_7.Init ();
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+  }
+
+  return;
+}
+
+static gpointer
+gst_asio_object_thread_func (GstAsioObject * self)
+{
+  HANDLE avrt_handle = nullptr;
+  static DWORD task_idx = 0;
+  HWND hwnd;
+  GSource *source = nullptr;
+  GSource *hwnd_msg_source = nullptr;
+  GIOChannel *msg_io_channel = nullptr;
+  HRESULT hr;
+  ASIOError asio_rst;
+  IASIO *asio_handle = nullptr;
+  GstAsioDeviceInfo *device_info = self->device_info;
+  /* FIXME: check more sample rate */
+  static ASIOSampleRate sample_rate_to_check[] = {
+    48000.0, 44100.0, 192000.0, 96000.0, 88200.0,
+  };
+
+  g_assert (device_info);
+
+  GST_INFO_OBJECT (self,
+      "Enter loop, ThreadingModel: %s, driver-name: %s, driver-desc: %s",
+      device_info->sta_model ? "STA" : "MTA",
+      GST_STR_NULL (device_info->driver_name),
+      GST_STR_NULL (device_info->driver_desc));
+
+  if (device_info->sta_model)
+    CoInitializeEx (NULL, COINIT_APARTMENTTHREADED);
+  else
+    CoInitializeEx (NULL, COINIT_MULTITHREADED);
+
+  /* Our thread is unlikely different from driver's working thread though,
+   * let's do this. It should not cause any problem */
+  AvSetMmThreadCharacteristicsW (L"Pro Audio", &task_idx);
+  g_main_context_push_thread_default (self->context);
+
+  source = g_idle_source_new ();
+  g_source_set_callback (source,
+      (GSourceFunc) gst_asio_object_main_loop_running_cb, self, nullptr);
+  g_source_attach (source, self->context);
+  g_source_unref (source);
+
+  /* XXX: not sure why ASIO API wants Windows handle for init().
+   * Possibly it might be used for STA COM threading
+   * but it's undocummented... */
+  hwnd = gst_asio_object_create_internal_hwnd (self);
+  if (!hwnd)
+    goto run_loop;
+
+  hr = CoCreateInstance (device_info->clsid, nullptr, CLSCTX_INPROC_SERVER,
+      device_info->clsid, (gpointer *) & asio_handle);
+  if (FAILED (hr)) {
+    GST_WARNING_OBJECT (self, "Failed to create IASIO instance, hr: 0x%x",
+        (guint) hr);
+    goto run_loop;
+  }
+
+  if (!asio_handle->init (hwnd)) {
+    GST_WARNING_OBJECT (self, "Failed to init IASIO instance");
+    asio_handle->Release ();
+    asio_handle = nullptr;
+    goto run_loop;
+  }
+
+  /* Query device information */
+  asio_rst = asio_handle->getChannels (&self->max_num_input_channels,
+      &self->max_num_output_channels);
+  if (asio_rst != 0) {
+    GST_WARNING_OBJECT (self, "Failed to query in/out channels, ret %ld",
+        asio_rst);
+    asio_handle->Release ();
+    asio_handle = nullptr;
+    goto run_loop;
+  }
+
+  GST_INFO_OBJECT (self, "Input/Output channles: %ld/%ld",
+      self->max_num_input_channels, self->max_num_output_channels);
+
+  asio_rst = asio_handle->getBufferSize (&self->min_buffer_size,
+      &self->max_buffer_size, &self->preferred_buffer_size,
+      &self->buffer_size_granularity);
+  if (asio_rst != 0) {
+    GST_WARNING_OBJECT (self, "Failed to get buffer size, ret %ld", asio_rst);
+    asio_handle->Release ();
+    asio_handle = nullptr;
+    goto run_loop;
+  }
+
+  /* Use preferreed buffer size by default */
+  self->selected_buffer_size = self->preferred_buffer_size;
+
+  GST_INFO_OBJECT (self, "min-buffer-size %ld, max-buffer-size %ld, "
+      "preferred-buffer-size %ld, buffer-size-granularity %ld",
+      self->min_buffer_size, self->max_buffer_size,
+      self->preferred_buffer_size, self->buffer_size_granularity);
+
+  for (guint i = 0; i < G_N_ELEMENTS (sample_rate_to_check); i++) {
+    asio_rst = asio_handle->canSampleRate (sample_rate_to_check[i]);
+    if (asio_rst != 0)
+      continue;
+
+    GST_INFO_OBJECT (self, "SampleRate %.1lf is supported",
+        sample_rate_to_check[i]);
+    g_array_append_val (self->supported_sample_rates, sample_rate_to_check[i]);
+  }
+
+  if (self->supported_sample_rates->len == 0) {
+    GST_WARNING_OBJECT (self, "Failed to query supported sample rate");
+    asio_handle->Release ();
+    asio_handle = nullptr;
+    goto run_loop;
+  }
+
+  /* Pick the first supported samplerate */
+  self->sample_rate =
+      g_array_index (self->supported_sample_rates, ASIOSampleRate, 0);
+  if (asio_handle->setSampleRate (self->sample_rate) != 0) {
+    GST_WARNING_OBJECT (self, "Failed to set samplerate %.1lf",
+        self->sample_rate);
+    asio_handle->Release ();
+    asio_handle = nullptr;
+    goto run_loop;
+  }
+
+  if (self->max_num_input_channels > 0) {
+    self->input_channel_infos = g_new0 (ASIOChannelInfo,
+        self->max_num_input_channels);
+    for (glong i = 0; i < self->max_num_input_channels; i++) {
+      ASIOChannelInfo *info = &self->input_channel_infos[i];
+      info->channel = i;
+      info->isInput = TRUE;
+
+      asio_rst = asio_handle->getChannelInfo (info);
+      if (asio_rst != 0) {
+        GST_WARNING_OBJECT (self, "Failed to %ld input channel info, ret %ld",
+            i, asio_rst);
+        asio_handle->Release ();
+        asio_handle = nullptr;
+        goto run_loop;
+      }
+
+      GST_INFO_OBJECT (self,
+          "InputChannelInfo %ld: isActive %s, channelGroup %ld, "
+          "ASIOSampleType %ld, name %s", i, info->isActive ? "true" : "false",
+          info->channelGroup, info->type, GST_STR_NULL (info->name));
+    }
+
+    self->input_channel_requested =
+        g_new0 (gboolean, self->max_num_input_channels);
+  }
+
+  if (self->max_num_output_channels > 0) {
+    self->output_channel_infos = g_new0 (ASIOChannelInfo,
+        self->max_num_output_channels);
+    for (glong i = 0; i < self->max_num_output_channels; i++) {
+      ASIOChannelInfo *info = &self->output_channel_infos[i];
+      info->channel = i;
+      info->isInput = FALSE;
+
+      asio_rst = asio_handle->getChannelInfo (info);
+      if (asio_rst != 0) {
+        GST_WARNING_OBJECT (self, "Failed to %ld output channel info, ret %ld",
+            i, asio_rst);
+        asio_handle->Release ();
+        asio_handle = nullptr;
+        goto run_loop;
+      }
+
+      GST_INFO_OBJECT (self,
+          "OutputChannelInfo %ld: isActive %s, channelGroup %ld, "
+          "ASIOSampleType %ld, name %s", i, info->isActive ? "true" : "false",
+          info->channelGroup, info->type, GST_STR_NULL (info->name));
+    }
+
+    self->output_channel_requested =
+        g_new0 (gboolean, self->max_num_input_channels);
+  }
+
+  asio_rst = asio_handle->getSampleRate (&self->sample_rate);
+  if (asio_rst != 0) {
+    GST_WARNING_OBJECT (self,
+        "Failed to get current samplerate, ret %ld", asio_rst);
+    asio_handle->Release ();
+    asio_handle = nullptr;
+    goto run_loop;
+  }
+
+  GST_INFO_OBJECT (self, "Current samplerate %.1lf", self->sample_rate);
+
+  self->callbacks = new GstAsioCallbacks (self);
+  if (!gst_asio_object_bind_callbacks (self)) {
+    GST_ERROR_OBJECT (self, "Failed to bind callback to slot");
+    delete self->callbacks;
+    self->callbacks = nullptr;
+
+    asio_handle->Release ();
+    asio_handle = nullptr;
+    goto run_loop;
+  }
+
+  msg_io_channel = g_io_channel_win32_new_messages ((guintptr) hwnd);
+  hwnd_msg_source = g_io_create_watch (msg_io_channel, G_IO_IN);
+  g_source_set_callback (hwnd_msg_source, (GSourceFunc) hwnd_msg_cb,
+      self->context, nullptr);
+  g_source_attach (hwnd_msg_source, self->context);
+
+  self->state = GST_ASIO_OBJECT_STATE_INITIALIZED;
+  self->asio_handle = asio_handle;
+
+run_loop:
+  g_main_loop_run (self->loop);
+
+  if (self->asio_handle) {
+    if (self->state > GST_ASIO_OBJECT_STATE_PREPARED)
+      self->asio_handle->stop ();
+
+    if (self->state > GST_ASIO_OBJECT_STATE_INITIALIZED)
+      self->asio_handle->disposeBuffers ();
+  }
+
+  gst_asio_object_unbind_callbacks (self);
+  if (self->callbacks) {
+    delete self->callbacks;
+    self->callbacks = nullptr;
+  }
+
+  if (hwnd_msg_source) {
+    g_source_destroy (hwnd_msg_source);
+    g_source_unref (hwnd_msg_source);
+  }
+
+  if (msg_io_channel)
+    g_io_channel_unref (msg_io_channel);
+
+  if (hwnd)
+    DestroyWindow (hwnd);
+
+  g_main_context_pop_thread_default (self->context);
+
+  if (avrt_handle)
+    AvRevertMmThreadCharacteristics (avrt_handle);
+
+  if (asio_handle) {
+    asio_handle->Release ();
+    asio_handle = nullptr;
+  }
+
+  CoUninitialize ();
+
+  GST_INFO_OBJECT (self, "Exit loop");
+
+  return nullptr;
+}
+
+static void
+gst_asio_object_weak_ref_notify (gpointer data, GstAsioObject * object)
+{
+  std::lock_guard < std::mutex > lk (global_lock);
+  asio_object_list = g_list_remove (asio_object_list, object);
+}
+
+GstAsioObject *
+gst_asio_object_new (const GstAsioDeviceInfo * info,
+    gboolean occupy_all_channels)
+{
+  GstAsioObject *self = nullptr;
+  GList *iter;
+  std::lock_guard < std::mutex > lk (global_lock);
+
+  g_return_val_if_fail (info != nullptr, nullptr);
+
+  /* Check if we have object corresponding to CLSID, and if so return
+   * already existing object instead of allocating new one */
+  for (iter = asio_object_list; iter; iter = g_list_next (iter)) {
+    GstAsioObject *object = (GstAsioObject *) iter->data;
+
+    if (object->device_info->clsid == info->clsid) {
+      GST_DEBUG_OBJECT (object, "Found configured ASIO object");
+      self = (GstAsioObject *) gst_object_ref (object);
+      break;
+    }
+  }
+
+  if (self)
+    return self;
+
+  self = (GstAsioObject *) g_object_new (GST_TYPE_ASIO_OBJECT,
+      "device-info", info, nullptr);
+
+  if (!self->asio_handle) {
+    GST_WARNING_OBJECT (self, "ASIO handle is not available");
+    gst_object_unref (self);
+
+    return nullptr;
+  }
+
+  self->occupy_all_channels = occupy_all_channels;
+
+  gst_object_ref_sink (self);
+
+  g_object_weak_ref (G_OBJECT (self),
+      (GWeakNotify) gst_asio_object_weak_ref_notify, nullptr);
+  asio_object_list = g_list_append (asio_object_list, self);
+
+  return self;
+}
+
+static GstCaps *
+gst_asio_object_create_caps_from_channel_info (GstAsioObject * self,
+    ASIOChannelInfo * info, guint min_num_channels, guint max_num_channels)
+{
+  GstCaps *caps;
+  std::string caps_str;
+  GstAudioFormat fmt;
+  const gchar *fmt_str;
+
+  g_assert (info);
+  g_assert (max_num_channels >= min_num_channels);
+
+  fmt = gst_asio_sample_type_to_gst (info->type);
+  if (fmt == GST_AUDIO_FORMAT_UNKNOWN) {
+    GST_ERROR_OBJECT (self, "Unknown format");
+    return nullptr;
+  }
+
+  fmt_str = gst_audio_format_to_string (fmt);
+
+  /* Actually we are non-interleaved, but element will interlave data */
+  caps_str = "audio/x-raw, layout = (string) interleaved, ";
+  caps_str += "format = (string) " + std::string (fmt_str) + ", ";
+  /* use fixated sample rate, otherwise get_caps/set_sample_rate() might
+   * be racy in case that multiple sink/src are used */
+  caps_str +=
+      "rate = (int) " + std::to_string ((gint) self->sample_rate) + ", ";
+
+  if (max_num_channels == min_num_channels)
+    caps_str += "channels = (int) " + std::to_string (max_num_channels);
+  else
+    caps_str += "channels = (int) [ " + std::to_string (min_num_channels) +
+        ", " + std::to_string (max_num_channels) + " ]";
+
+  caps = gst_caps_from_string (caps_str.c_str ());
+  if (!caps) {
+    GST_ERROR_OBJECT (self, "Failed to create caps");
+    return nullptr;
+  }
+
+  GST_DEBUG_OBJECT (self, "Create caps %" GST_PTR_FORMAT, caps);
+
+  return caps;
+}
+
+/* FIXME: assuming all channels has the same format but it might not be true? */
+GstCaps *
+gst_asio_object_get_caps (GstAsioObject * obj, GstAsioDeviceClassType type,
+    guint min_num_channels, guint max_num_channels)
+{
+  ASIOChannelInfo *infos;
+
+  g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), nullptr);
+
+  if (type == GST_ASIO_DEVICE_CLASS_CAPTURE) {
+    if (obj->max_num_input_channels == 0) {
+      GST_WARNING_OBJECT (obj, "Device doesn't support input");
+      return nullptr;
+    }
+
+    /* max_num_channels == 0 means [1, max-allowed-channles] */
+    if (max_num_channels > 0) {
+      if (max_num_channels > obj->max_num_input_channels) {
+        GST_WARNING_OBJECT (obj, "Too many max channels");
+        return nullptr;
+      }
+    } else {
+      max_num_channels = obj->max_num_input_channels;
+    }
+
+    if (min_num_channels > 0) {
+      if (min_num_channels > obj->max_num_input_channels) {
+        GST_WARNING_OBJECT (obj, "Too many min channels");
+        return nullptr;
+      }
+    } else {
+      min_num_channels = 1;
+    }
+
+    infos = obj->input_channel_infos;
+  } else {
+    if (obj->max_num_output_channels == 0) {
+      GST_WARNING_OBJECT (obj, "Device doesn't support output");
+      return nullptr;
+    }
+
+    /* max_num_channels == 0 means [1, max-allowed-channles] */
+    if (max_num_channels > 0) {
+      if (max_num_channels > obj->max_num_output_channels) {
+        GST_WARNING_OBJECT (obj, "Too many max channels");
+        return nullptr;
+      }
+    } else {
+      max_num_channels = obj->max_num_output_channels;
+    }
+
+    if (min_num_channels > 0) {
+      if (min_num_channels > obj->max_num_output_channels) {
+        GST_WARNING_OBJECT (obj, "Too many min channels");
+        return nullptr;
+      }
+    } else {
+      min_num_channels = 1;
+    }
+
+    infos = obj->output_channel_infos;
+  }
+
+  return gst_asio_object_create_caps_from_channel_info (obj,
+      infos, min_num_channels, max_num_channels);
+}
+
+gboolean
+gst_asio_object_get_max_num_channels (GstAsioObject * obj, glong * num_input_ch,
+    glong * num_output_ch)
+{
+  g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
+
+  if (num_input_ch)
+    *num_input_ch = obj->max_num_input_channels;
+  if (num_output_ch)
+    *num_output_ch = obj->max_num_output_channels;
+
+  return TRUE;
+}
+
+gboolean
+gst_asio_object_get_buffer_size (GstAsioObject * obj, glong * min_size,
+    glong * max_size, glong * preferred_size, glong * granularity)
+{
+  g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
+
+  if (min_size)
+    *min_size = obj->min_buffer_size;
+  if (max_size)
+    *max_size = obj->max_buffer_size;
+  if (preferred_size)
+    *preferred_size = obj->preferred_buffer_size;
+  if (granularity)
+    *granularity = obj->buffer_size_granularity;
+
+  return TRUE;
+}
+
+typedef void (*GstAsioObjectThreadFunc) (GstAsioObject * obj, gpointer data);
+
+typedef struct
+{
+  GstAsioObject *self;
+  GstAsioObjectThreadFunc func;
+  gpointer data;
+  gboolean fired;
+} GstAsioObjectThreadRunData;
+
+static gboolean
+gst_asio_object_thread_run_func (GstAsioObjectThreadRunData * data)
+{
+  GstAsioObject *self = data->self;
+
+  if (data->func)
+    data->func (self, data->data);
+
+  g_mutex_lock (&self->thread_lock);
+  data->fired = TRUE;
+  g_cond_broadcast (&self->thread_cond);
+  g_mutex_unlock (&self->thread_lock);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+gst_asio_object_thread_add (GstAsioObject * self, GstAsioObjectThreadFunc func,
+    gpointer data)
+{
+  GstAsioObjectThreadRunData thread_data;
+
+  g_return_if_fail (GST_IS_ASIO_OBJECT (self));
+
+  thread_data.self = self;
+  thread_data.func = func;
+  thread_data.data = data;
+  thread_data.fired = FALSE;
+
+  g_main_context_invoke (self->context,
+      (GSourceFunc) gst_asio_object_thread_run_func, &thread_data);
+
+  g_mutex_lock (&self->thread_lock);
+  while (!thread_data.fired)
+    g_cond_wait (&self->thread_cond, &self->thread_lock);
+  g_mutex_unlock (&self->thread_lock);
+}
+
+static gboolean
+gst_asio_object_validate_channels (GstAsioObject * self, gboolean is_input,
+    guint * channel_indices, guint num_channels)
+{
+  if (is_input) {
+    if (self->max_num_input_channels < num_channels) {
+      GST_WARNING_OBJECT (self, "%d exceeds max input channels %ld",
+          num_channels, self->max_num_input_channels);
+      return FALSE;
+    }
+
+    for (guint i = 0; i < num_channels; i++) {
+      guint ch = channel_indices[i];
+      if (self->max_num_input_channels <= ch) {
+        GST_WARNING_OBJECT (self, "%d exceeds max input channels %ld",
+            ch, self->max_num_input_channels);
+
+        return FALSE;
+      }
+    }
+  } else {
+    if (self->max_num_output_channels < num_channels) {
+      GST_WARNING_OBJECT (self, "%d exceeds max output channels %ld",
+          num_channels, self->max_num_output_channels);
+
+      return FALSE;
+    }
+
+    for (guint i = 0; i < num_channels; i++) {
+      guint ch = channel_indices[i];
+      if (self->max_num_output_channels <= ch) {
+        GST_WARNING_OBJECT (self, "%d exceeds max output channels %ld",
+            ch, self->max_num_output_channels);
+
+        return FALSE;
+      }
+    }
+  }
+
+  return TRUE;
+}
+
+static gboolean
+gst_asio_object_check_buffer_reuse (GstAsioObject * self, ASIOBool is_input,
+    guint * channel_indices, guint num_channels)
+{
+  guint num_found = 0;
+
+  g_assert (self->buffer_infos);
+  g_assert (self->num_allocated_buffers > 0);
+
+  for (guint i = 0; i < self->num_allocated_buffers; i++) {
+    ASIOBufferInfo *info = &self->buffer_infos[i];
+
+    if (info->isInput != is_input)
+      continue;
+
+    for (guint j = 0; j < num_channels; j++) {
+      if (info->channelNum == channel_indices[j]) {
+        num_found++;
+
+        break;
+      }
+    }
+  }
+
+  return num_found == num_channels;
+}
+
+static void
+gst_asio_object_dispose_buffers_async (GstAsioObject * self, ASIOError * rst)
+{
+  g_assert (self->asio_handle);
+  g_assert (rst);
+
+  *rst = self->asio_handle->disposeBuffers ();
+}
+
+static gboolean
+gst_asio_object_dispose_buffers (GstAsioObject * self)
+{
+  ASIOError rst;
+  g_assert (self->asio_handle);
+
+  if (!self->buffer_infos)
+    return TRUE;
+
+  if (!self->device_info->sta_model) {
+    rst = self->asio_handle->disposeBuffers ();
+  } else {
+    gst_asio_object_thread_add (self,
+        (GstAsioObjectThreadFunc) gst_asio_object_dispose_buffers_async, &rst);
+  }
+
+  g_clear_pointer (&self->buffer_infos, g_free);
+  self->num_allocated_buffers = 0;
+
+  return rst == 0;
+}
+
+static ASIOError
+gst_asio_object_create_buffers_real (GstAsioObject * self, glong * buffer_size)
+{
+  ASIOError err;
+
+  g_assert (buffer_size);
+
+  err = self->asio_handle->createBuffers (self->buffer_infos,
+      self->num_requested_input_channels + self->num_requested_output_channels,
+      *buffer_size, &self->driver_callbacks);
+
+  /* It failed and buffer size is not equal to preferred size,
+   * try again with preferred size */
+  if (err != 0 && *buffer_size != self->preferred_buffer_size) {
+    GST_WARNING_OBJECT (self,
+        "Failed to create buffer with buffer size %ld, try again with %ld",
+        *buffer_size, self->preferred_buffer_size);
+
+    err = self->asio_handle->createBuffers (self->buffer_infos,
+        self->num_requested_input_channels +
+        self->num_requested_output_channels, self->preferred_buffer_size,
+        &self->driver_callbacks);
+
+    if (!err) {
+      *buffer_size = self->preferred_buffer_size;
+    }
+  }
+
+  return err;
+}
+
+typedef struct
+{
+  glong buffer_size;
+  ASIOError err;
+} CreateBuffersAsyncData;
+
+static void
+gst_asio_object_create_buffers_async (GstAsioObject * self,
+    CreateBuffersAsyncData * data)
+{
+  data->err = gst_asio_object_create_buffers_real (self, &data->buffer_size);
+}
+
+static gboolean
+gst_asio_object_create_buffers_internal (GstAsioObject * self,
+    glong * buffer_size)
+{
+  ASIOError err;
+  g_assert (self->asio_handle);
+
+  if (!self->device_info->sta_model) {
+    err = gst_asio_object_create_buffers_real (self, buffer_size);
+  } else {
+    CreateBuffersAsyncData data;
+    data.buffer_size = *buffer_size;
+
+    gst_asio_object_thread_add (self,
+        (GstAsioObjectThreadFunc) gst_asio_object_create_buffers_async, &data);
+
+    err = data.err;
+    *buffer_size = data.buffer_size;
+  }
+
+  return !err;
+}
+
+gboolean
+gst_asio_object_create_buffers (GstAsioObject * obj,
+    GstAsioDeviceClassType type,
+    guint * channel_indices, guint num_channels, guint * buffer_size)
+{
+  gboolean can_reuse = FALSE;
+  guint i, j;
+  glong buf_size;
+  glong prev_buf_size = 0;
+  gboolean is_src;
+
+  g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
+  g_return_val_if_fail (channel_indices != nullptr, FALSE);
+  g_return_val_if_fail (num_channels > 0, FALSE);
+
+  GST_DEBUG_OBJECT (obj, "Create buffers");
+
+  if (type == GST_ASIO_DEVICE_CLASS_CAPTURE)
+    is_src = TRUE;
+  else
+    is_src = FALSE;
+
+  g_mutex_lock (&obj->api_lock);
+  if (!gst_asio_object_validate_channels (obj, is_src, channel_indices,
+          num_channels)) {
+    GST_ERROR_OBJECT (obj, "Invalid request");
+    g_mutex_unlock (&obj->api_lock);
+
+    return FALSE;
+  }
+
+  if (obj->buffer_infos) {
+    GST_DEBUG_OBJECT (obj,
+        "Have configured buffer infors, checking whether we can reuse it");
+    can_reuse = gst_asio_object_check_buffer_reuse (obj,
+        is_src ? TRUE : FALSE, channel_indices, num_channels);
+  }
+
+  if (can_reuse) {
+    GST_DEBUG_OBJECT (obj, "We can reuse already allocated buffers");
+    if (buffer_size)
+      *buffer_size = obj->selected_buffer_size;
+
+    g_mutex_unlock (&obj->api_lock);
+
+    return TRUE;
+  }
+
+  /* Cannot re-allocated buffers once started... */
+  if (obj->state > GST_ASIO_OBJECT_STATE_PREPARED) {
+    GST_WARNING_OBJECT (obj, "We are running already");
+    g_mutex_unlock (&obj->api_lock);
+
+    return FALSE;
+  }
+
+  /* Use already configured buffer size */
+  if (obj->buffer_infos)
+    prev_buf_size = obj->selected_buffer_size;
+
+  /* If we have configured buffers, dispose and re-allocate */
+  if (!gst_asio_object_dispose_buffers (obj)) {
+    GST_ERROR_OBJECT (obj, "Failed to dispose buffers");
+
+    obj->state = GST_ASIO_OBJECT_STATE_INITIALIZED;
+
+    g_mutex_unlock (&obj->api_lock);
+    return FALSE;
+  }
+
+  if (obj->occupy_all_channels) {
+    GST_INFO_OBJECT (obj,
+        "occupy-all-channels mode, will allocate buffers for all channels");
+    /* In this case, we will allocate buffer for all available input/output
+     * channles, regardless of what requested here */
+    for (guint i = 0; i < (guint) obj->max_num_input_channels; i++)
+      obj->input_channel_requested[i] = TRUE;
+    for (guint i = 0; i < (guint) obj->max_num_output_channels; i++)
+      obj->output_channel_requested[i] = TRUE;
+
+    obj->num_requested_input_channels = obj->max_num_input_channels;
+    obj->num_requested_output_channels = obj->max_num_output_channels;
+  } else {
+    if (is_src) {
+      for (guint i = 0; i < num_channels; i++) {
+        guint ch = channel_indices[i];
+
+        obj->input_channel_requested[ch] = TRUE;
+      }
+
+      obj->num_requested_input_channels = 0;
+      for (guint i = 0; i < obj->max_num_input_channels; i++) {
+        if (obj->input_channel_requested[i])
+          obj->num_requested_input_channels++;
+      }
+    } else {
+      for (guint i = 0; i < num_channels; i++) {
+        guint ch = channel_indices[i];
+
+        obj->output_channel_requested[ch] = TRUE;
+      }
+
+      obj->num_requested_output_channels = 0;
+      for (guint i = 0; i < obj->max_num_output_channels; i++) {
+        if (obj->output_channel_requested[i])
+          obj->num_requested_output_channels++;
+      }
+    }
+  }
+
+  obj->num_allocated_buffers = obj->num_requested_input_channels +
+      obj->num_requested_output_channels;
+
+  obj->buffer_infos = g_new0 (ASIOBufferInfo, obj->num_allocated_buffers);
+  for (i = 0, j = 0; i < obj->num_requested_input_channels; i++) {
+    ASIOBufferInfo *info = &obj->buffer_infos[i];
+
+    info->isInput = TRUE;
+    while (!obj->input_channel_requested[j])
+      j++;
+
+    info->channelNum = j;
+    j++;
+  }
+
+  for (i = obj->num_requested_input_channels, j = 0;
+      i <
+      obj->num_requested_input_channels + obj->num_requested_output_channels;
+      i++) {
+    ASIOBufferInfo *info = &obj->buffer_infos[i];
+
+    info->isInput = FALSE;
+    while (!obj->output_channel_requested[j])
+      j++;
+
+    info->channelNum = j;
+    j++;
+  }
+
+  if (prev_buf_size > 0) {
+    buf_size = prev_buf_size;
+  } else if (buffer_size && *buffer_size > 0) {
+    buf_size = *buffer_size;
+  } else {
+    buf_size = obj->preferred_buffer_size;
+  }
+
+  GST_INFO_OBJECT (obj, "Creating buffer with size %ld", buf_size);
+
+  if (!gst_asio_object_create_buffers_internal (obj, &buf_size)) {
+    GST_ERROR_OBJECT (obj, "Failed to create buffers");
+    g_clear_pointer (&obj->buffer_infos, g_free);
+    obj->num_allocated_buffers = 0;
+
+    obj->state = GST_ASIO_OBJECT_STATE_INITIALIZED;
+
+    g_mutex_unlock (&obj->api_lock);
+
+    return FALSE;
+  }
+
+  GST_INFO_OBJECT (obj, "Selected buffer size %ld", buf_size);
+
+  obj->selected_buffer_size = buf_size;
+  if (buffer_size)
+    *buffer_size = buf_size;
+
+  obj->state = GST_ASIO_OBJECT_STATE_PREPARED;
+
+  g_mutex_unlock (&obj->api_lock);
+
+  return TRUE;
+}
+
+typedef struct
+{
+  glong arg[4];
+  ASIOError ret;
+} RunAsyncData;
+
+static void
+gst_asio_object_get_latencies_async (GstAsioObject * self, RunAsyncData * data)
+{
+  data->ret = self->asio_handle->getLatencies (&data->arg[0], &data->arg[1]);
+}
+
+gboolean
+gst_asio_object_get_latencies (GstAsioObject * obj, glong * input_latency,
+    glong * output_latency)
+{
+  RunAsyncData data = { 0 };
+  ASIOError err;
+
+  g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
+  g_assert (obj->asio_handle);
+
+  if (!obj->device_info->sta_model) {
+    err = obj->asio_handle->getLatencies (input_latency, output_latency);
+  } else {
+    gst_asio_object_thread_add (obj,
+        (GstAsioObjectThreadFunc) gst_asio_object_get_latencies_async, &data);
+
+    *input_latency = data.arg[0];
+    *output_latency = data.arg[1];
+    err = data.ret;
+  }
+
+  return !err;
+}
+
+typedef struct
+{
+  ASIOSampleRate sample_rate;
+  ASIOError err;
+} SampleRateAsyncData;
+
+static void
+gst_asio_object_can_sample_rate_async (GstAsioObject * self,
+    SampleRateAsyncData * data)
+{
+  data->err = self->asio_handle->canSampleRate (data->sample_rate);
+}
+
+gboolean
+gst_asio_object_can_sample_rate (GstAsioObject * obj,
+    ASIOSampleRate sample_rate)
+{
+  SampleRateAsyncData data = { 0 };
+  ASIOError err = 0;
+
+  g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
+  g_assert (obj->asio_handle);
+
+  g_mutex_lock (&obj->api_lock);
+  for (guint i = 0; i < obj->supported_sample_rates->len; i++) {
+    ASIOSampleRate val = g_array_index (obj->supported_sample_rates,
+        ASIOSampleRate, i);
+    if (val == sample_rate) {
+      g_mutex_unlock (&obj->api_lock);
+      return TRUE;
+    }
+  }
+
+  if (!obj->device_info->sta_model) {
+    err = obj->asio_handle->canSampleRate (sample_rate);
+
+    if (!err)
+      g_array_append_val (obj->supported_sample_rates, sample_rate);
+
+    g_mutex_unlock (&obj->api_lock);
+    return !err;
+  }
+
+  data.sample_rate = sample_rate;
+  gst_asio_object_thread_add (obj,
+      (GstAsioObjectThreadFunc) gst_asio_object_can_sample_rate_async, &data);
+
+  if (!data.err)
+    g_array_append_val (obj->supported_sample_rates, sample_rate);
+
+  g_mutex_unlock (&obj->api_lock);
+
+  return !data.err;
+}
+
+gboolean
+gst_asio_object_get_sample_rate (GstAsioObject * obj,
+    ASIOSampleRate * sample_rate)
+{
+  g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
+
+  *sample_rate = obj->sample_rate;
+
+  return 0;
+}
+
+static void
+gst_asio_object_set_sample_rate_async (GstAsioObject * self,
+    SampleRateAsyncData * data)
+{
+  data->err = self->asio_handle->setSampleRate (data->sample_rate);
+  if (!data->err)
+    self->sample_rate = data->sample_rate;
+}
+
+gboolean
+gst_asio_object_set_sample_rate (GstAsioObject * obj,
+    ASIOSampleRate sample_rate)
+{
+  SampleRateAsyncData data = { 0 };
+  ASIOError err = 0;
+
+  g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
+  g_assert (obj->asio_handle);
+
+  g_mutex_lock (&obj->api_lock);
+  if (sample_rate == obj->sample_rate) {
+    g_mutex_unlock (&obj->api_lock);
+    return TRUE;
+  }
+
+  if (!obj->device_info->sta_model) {
+    err = obj->asio_handle->setSampleRate (sample_rate);
+    if (!err)
+      obj->sample_rate = sample_rate;
+
+    g_mutex_unlock (&obj->api_lock);
+    return !err;
+  }
+
+  data.sample_rate = sample_rate;
+  gst_asio_object_thread_add (obj,
+      (GstAsioObjectThreadFunc) gst_asio_object_set_sample_rate_async, &data);
+  g_mutex_unlock (&obj->api_lock);
+
+  return !data.err;
+}
+
+static void
+gst_asio_object_buffer_switch (GstAsioObject * self,
+    glong index, ASIOBool process_now)
+{
+  ASIOTime time_info;
+  ASIOTime *our_time_info = nullptr;
+  ASIOError err = 0;
+
+  memset (&time_info, 0, sizeof (ASIOTime));
+
+  err =
+      self->asio_handle->getSamplePosition (&time_info.timeInfo.samplePosition,
+      &time_info.timeInfo.systemTime);
+  if (!err)
+    our_time_info = &time_info;
+
+  gst_asio_object_buffer_switch_time_info (self,
+      our_time_info, index, process_now);
+}
+
+static void
+gst_asio_object_sample_rate_changed (GstAsioObject * self, ASIOSampleRate rate)
+{
+  GST_INFO_OBJECT (self, "SampleRate changed to %lf", rate);
+}
+
+static glong
+gst_asio_object_messages (GstAsioObject * self,
+    glong selector, glong value, gpointer message, gdouble * opt)
+{
+  GST_DEBUG_OBJECT (self, "ASIO message: %ld, %ld", selector, value);
+
+  switch (selector) {
+    case kAsioSelectorSupported:
+      if (value == kAsioResetRequest || value == kAsioEngineVersion ||
+          value == kAsioResyncRequest || value == kAsioLatenciesChanged ||
+          value == kAsioSupportsTimeCode || value == kAsioSupportsInputMonitor)
+        return 0;
+      else if (value == kAsioSupportsTimeInfo)
+        return 1;
+      GST_WARNING_OBJECT (self, "Unsupported ASIO selector: %li", value);
+      break;
+    case kAsioBufferSizeChange:
+      GST_WARNING_OBJECT (self,
+          "Unsupported ASIO message: kAsioBufferSizeChange");
+      break;
+    case kAsioResetRequest:
+      GST_WARNING_OBJECT (self, "Unsupported ASIO message: kAsioResetRequest");
+      break;
+    case kAsioResyncRequest:
+      GST_WARNING_OBJECT (self, "Unsupported ASIO message: kAsioResyncRequest");
+      break;
+    case kAsioLatenciesChanged:
+      GST_WARNING_OBJECT (self,
+          "Unsupported ASIO message: kAsioLatenciesChanged");
+      break;
+    case kAsioEngineVersion:
+      /* We target the ASIO v2 API, which includes ASIOOutputReady() */
+      return 2;
+    case kAsioSupportsTimeInfo:
+      /* We use the new time info buffer switch callback */
+      return 1;
+    case kAsioSupportsTimeCode:
+      /* We don't use the time code info right now */
+      return 0;
+    default:
+      GST_WARNING_OBJECT (self, "Unsupported ASIO message: %li, %li", selector,
+          value);
+      break;
+  }
+
+  return 0;
+}
+
+#define PACK_ASIO_64(v) ((v).lo | ((guint64)((v).hi) << 32))
+
+static ASIOTime *
+gst_asio_object_buffer_switch_time_info (GstAsioObject * self,
+    ASIOTime * time_info, glong index, ASIOBool process_now)
+{
+  GList *iter;
+
+  if (time_info) {
+    guint64 pos;
+    guint64 system_time;
+
+    pos = PACK_ASIO_64 (time_info->timeInfo.samplePosition);
+    system_time = PACK_ASIO_64 (time_info->timeInfo.systemTime);
+
+    GST_TRACE_OBJECT (self, "Sample Position: %" G_GUINT64_FORMAT
+        ", System Time: %" GST_TIME_FORMAT, pos, GST_TIME_ARGS (system_time));
+  }
+
+  g_mutex_lock (&self->api_lock);
+  if (!self->src_client_callbacks && !self->sink_client_callbacks &&
+      !self->loopback_client_callbacks) {
+    GST_WARNING_OBJECT (self, "No installed client callback");
+    goto out;
+  }
+
+  for (iter = self->src_client_callbacks; iter;) {
+    GstAsioObjectCallbacksPrivate *cb =
+        (GstAsioObjectCallbacksPrivate *) iter->data;
+    gboolean ret;
+
+    ret = cb->callbacks.buffer_switch (self, index, self->buffer_infos,
+        self->num_allocated_buffers, self->input_channel_infos,
+        self->output_channel_infos, self->sample_rate,
+        self->selected_buffer_size, time_info, cb->callbacks.user_data);
+    if (!ret) {
+      GST_INFO_OBJECT (self, "Remove callback for id %" G_GUINT64_FORMAT,
+          cb->callback_id);
+      GList *to_remove = iter;
+      iter = g_list_next (iter);
+
+      g_free (to_remove->data);
+      g_list_free (to_remove);
+    }
+
+    iter = g_list_next (iter);
+  }
+
+  for (iter = self->sink_client_callbacks; iter;) {
+    GstAsioObjectCallbacksPrivate *cb =
+        (GstAsioObjectCallbacksPrivate *) iter->data;
+    gboolean ret;
+
+    ret = cb->callbacks.buffer_switch (self, index, self->buffer_infos,
+        self->num_allocated_buffers, self->input_channel_infos,
+        self->output_channel_infos, self->sample_rate,
+        self->selected_buffer_size, time_info, cb->callbacks.user_data);
+    if (!ret) {
+      GST_INFO_OBJECT (self, "Remove callback for id %" G_GUINT64_FORMAT,
+          cb->callback_id);
+      GList *to_remove = iter;
+      iter = g_list_next (iter);
+
+      g_free (to_remove->data);
+      g_list_free (to_remove);
+    }
+
+    iter = g_list_next (iter);
+  }
+
+  for (iter = self->loopback_client_callbacks; iter;) {
+    GstAsioObjectCallbacksPrivate *cb =
+        (GstAsioObjectCallbacksPrivate *) iter->data;
+    gboolean ret;
+
+    ret = cb->callbacks.buffer_switch (self, index, self->buffer_infos,
+        self->num_allocated_buffers, self->input_channel_infos,
+        self->output_channel_infos, self->sample_rate,
+        self->selected_buffer_size, time_info, cb->callbacks.user_data);
+    if (!ret) {
+      GST_INFO_OBJECT (self, "Remove callback for id %" G_GUINT64_FORMAT,
+          cb->callback_id);
+      GList *to_remove = iter;
+      iter = g_list_next (iter);
+
+      g_free (to_remove->data);
+      g_list_free (to_remove);
+    }
+
+    iter = g_list_next (iter);
+  }
+
+  self->asio_handle->outputReady ();
+
+out:
+  g_mutex_unlock (&self->api_lock);
+
+  return nullptr;
+}
+
+static void
+gst_asio_object_start_async (GstAsioObject * self, ASIOError * rst)
+{
+  *rst = self->asio_handle->start ();
+}
+
+gboolean
+gst_asio_object_start (GstAsioObject * obj)
+{
+  ASIOError ret;
+
+  g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
+
+  g_mutex_lock (&obj->api_lock);
+  if (obj->state > GST_ASIO_OBJECT_STATE_PREPARED) {
+    GST_DEBUG_OBJECT (obj, "We are running already");
+    g_mutex_unlock (&obj->api_lock);
+
+    return TRUE;
+  } else if (obj->state < GST_ASIO_OBJECT_STATE_PREPARED) {
+    GST_ERROR_OBJECT (obj, "We are not prepared");
+    g_mutex_unlock (&obj->api_lock);
+
+    return FALSE;
+  }
+
+  /* Then start */
+  if (!obj->device_info->sta_model) {
+    ret = obj->asio_handle->start ();
+  } else {
+    gst_asio_object_thread_add (obj,
+        (GstAsioObjectThreadFunc) gst_asio_object_start_async, &ret);
+  }
+
+  if (ret != 0) {
+    GST_ERROR_OBJECT (obj, "Failed to start object");
+    g_mutex_unlock (&obj->api_lock);
+
+    return FALSE;
+  }
+
+  obj->state = GST_ASIO_OBJECT_STATE_RUNNING;
+  g_mutex_unlock (&obj->api_lock);
+
+  return TRUE;
+}
+
+gboolean
+gst_asio_object_install_callback (GstAsioObject * obj,
+    GstAsioDeviceClassType type,
+    GstAsioObjectCallbacks * callbacks, guint64 * callback_id)
+{
+  GstAsioObjectCallbacksPrivate *cb;
+
+  g_return_val_if_fail (GST_IS_ASIO_OBJECT (obj), FALSE);
+  g_return_val_if_fail (callbacks != nullptr, FALSE);
+  g_return_val_if_fail (callback_id != nullptr, FALSE);
+
+  g_mutex_lock (&obj->api_lock);
+  cb = g_new0 (GstAsioObjectCallbacksPrivate, 1);
+  cb->callbacks = *callbacks;
+  cb->callback_id = obj->next_callback_id;
+
+  switch (type) {
+    case GST_ASIO_DEVICE_CLASS_CAPTURE:
+      obj->src_client_callbacks = g_list_append (obj->src_client_callbacks, cb);
+      break;
+    case GST_ASIO_DEVICE_CLASS_RENDER:
+      obj->sink_client_callbacks =
+          g_list_append (obj->sink_client_callbacks, cb);
+      break;
+    case GST_ASIO_DEVICE_CLASS_LOOPBACK_CAPTURE:
+      obj->loopback_client_callbacks =
+          g_list_append (obj->loopback_client_callbacks, cb);
+      break;
+    default:
+      g_assert_not_reached ();
+      g_free (cb);
+      return FALSE;
+  }
+
+  *callback_id = cb->callback_id;
+  g_mutex_unlock (&obj->api_lock);
+
+  return TRUE;
+}
+
+void
+gst_asio_object_uninstall_callback (GstAsioObject * obj, guint64 callback_id)
+{
+  GList *iter;
+
+  g_return_if_fail (GST_IS_ASIO_OBJECT (obj));
+
+  g_mutex_lock (&obj->api_lock);
+
+  GST_DEBUG_OBJECT (obj, "Removing callback id %" G_GUINT64_FORMAT,
+      callback_id);
+
+  for (iter = obj->src_client_callbacks; iter; iter = g_list_next (iter)) {
+    GstAsioObjectCallbacksPrivate *cb =
+        (GstAsioObjectCallbacksPrivate *) iter->data;
+
+    if (cb->callback_id != callback_id)
+      continue;
+
+    GST_DEBUG_OBJECT (obj, "Found src callback for id %" G_GUINT64_FORMAT,
+        callback_id);
+
+    obj->src_client_callbacks =
+        g_list_remove_link (obj->src_client_callbacks, iter);
+    g_free (iter->data);
+    g_list_free (iter);
+    g_mutex_unlock (&obj->api_lock);
+
+    return;
+  }
+
+  for (iter = obj->sink_client_callbacks; iter; iter = g_list_next (iter)) {
+    GstAsioObjectCallbacksPrivate *cb =
+        (GstAsioObjectCallbacksPrivate *) iter->data;
+
+    if (cb->callback_id != callback_id)
+      continue;
+
+    GST_DEBUG_OBJECT (obj, "Found sink callback for id %" G_GUINT64_FORMAT,
+        callback_id);
+
+    obj->sink_client_callbacks =
+        g_list_remove_link (obj->sink_client_callbacks, iter);
+    g_free (iter->data);
+    g_list_free (iter);
+    g_mutex_unlock (&obj->api_lock);
+
+    return;
+  }
+
+  for (iter = obj->loopback_client_callbacks; iter; iter = g_list_next (iter)) {
+    GstAsioObjectCallbacksPrivate *cb =
+        (GstAsioObjectCallbacksPrivate *) iter->data;
+
+    if (cb->callback_id != callback_id)
+      continue;
+
+    GST_DEBUG_OBJECT (obj, "Found loopback callback for id %" G_GUINT64_FORMAT,
+        callback_id);
+
+    obj->loopback_client_callbacks =
+        g_list_remove_link (obj->loopback_client_callbacks, iter);
+    g_free (iter->data);
+    g_list_free (iter);
+    break;
+  }
+
+  g_mutex_unlock (&obj->api_lock);
+}
diff --git a/sys/asio/gstasioobject.h b/sys/asio/gstasioobject.h
new file mode 100644 (file)
index 0000000..7d80b7b
--- /dev/null
@@ -0,0 +1,104 @@
+/* GStreamer
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License aglong with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_ASIO_OBJECT_H__
+#define __GST_ASIO_OBJECT_H__
+
+#include <gst/gst.h>
+#include <windows.h>
+#include "gstasioutils.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_ASIO_OBJECT (gst_asio_object_get_type())
+G_DECLARE_FINAL_TYPE (GstAsioObject, gst_asio_object,
+    GST, ASIO_OBJECT, GstObject);
+
+typedef struct {
+  gboolean (*buffer_switch) (GstAsioObject * obj,
+                             glong index,
+                             ASIOBufferInfo * infos,
+                             guint num_infos,
+                             ASIOChannelInfo * input_channel_infos,
+                             ASIOChannelInfo * output_channel_infos,
+                             ASIOSampleRate sample_rate,
+                             glong buffer_size,
+                             ASIOTime * time_info,
+                             gpointer user_data);
+
+  gpointer user_data;
+} GstAsioObjectCallbacks;
+
+typedef enum
+{
+  GST_ASIO_DEVICE_CLASS_CAPTURE,
+  GST_ASIO_DEVICE_CLASS_RENDER,
+  GST_ASIO_DEVICE_CLASS_LOOPBACK_CAPTURE,
+} GstAsioDeviceClassType;
+
+GstAsioObject * gst_asio_object_new (const GstAsioDeviceInfo * info,
+                                     gboolean occupy_all_channels);
+
+GstCaps       * gst_asio_object_get_caps         (GstAsioObject * obj,
+                                                  GstAsioDeviceClassType type,
+                                                  guint num_min_channels,
+                                                  guint num_max_channels);
+
+gboolean        gst_asio_object_create_buffers (GstAsioObject * obj,
+                                                GstAsioDeviceClassType type,
+                                                guint * channel_indices,
+                                                guint num_channels,
+                                                guint * buffer_size);
+
+gboolean        gst_asio_object_start           (GstAsioObject * obj);
+
+gboolean        gst_asio_object_install_callback (GstAsioObject * obj,
+                                                  GstAsioDeviceClassType type,
+                                                  GstAsioObjectCallbacks * callbacks,
+                                                  guint64 * callback_id);
+
+void            gst_asio_object_uninstall_callback (GstAsioObject * obj,
+                                                    guint64 callback_id);
+
+gboolean        gst_asio_object_get_max_num_channels (GstAsioObject * obj,
+                                                      glong * num_input_ch,
+                                                      glong * num_output_ch);
+
+gboolean        gst_asio_object_get_buffer_size (GstAsioObject * obj,
+                                                 glong * min_size,
+                                                 glong * max_size,
+                                                 glong * preferred_size,
+                                                 glong * granularity);
+
+gboolean        gst_asio_object_get_latencies (GstAsioObject * obj,
+                                               glong * input_latency,
+                                               glong * output_latency);
+
+gboolean        gst_asio_object_can_sample_rate (GstAsioObject * obj,
+                                                 ASIOSampleRate sample_rate);
+
+gboolean        gst_asio_object_get_sample_rate (GstAsioObject * obj,
+                                                 ASIOSampleRate * sample_rate);
+
+gboolean        gst_asio_object_set_sample_rate (GstAsioObject * obj,
+                                                 ASIOSampleRate sample_rate);
+
+G_END_DECLS
+
+#endif /* __GST_ASIO_OBJECT_H__ */
\ No newline at end of file
diff --git a/sys/asio/gstasioringbuffer.cpp b/sys/asio/gstasioringbuffer.cpp
new file mode 100644 (file)
index 0000000..449d54b
--- /dev/null
@@ -0,0 +1,473 @@
+/* GStreamer
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+#include "gstasioringbuffer.h"
+#include <string.h>
+#include "gstasioutils.h"
+#include "gstasioobject.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_asio_ring_buffer_debug);
+#define GST_CAT_DEFAULT gst_asio_ring_buffer_debug
+
+struct _GstAsioRingBuffer
+{
+  GstAudioRingBuffer parent;
+
+  GstAsioDeviceClassType type;
+
+  GstAsioObject *asio_object;
+  guint *channel_indices;
+  guint num_channels;
+  ASIOBufferInfo **infos;
+
+  guint64 callback_id;
+  gboolean callback_installed;
+
+  gboolean running;
+  guint buffer_size;
+
+  /* Used to detect sample gap */
+  gboolean is_first;
+  guint64 expected_sample_position;
+  gboolean trace_sample_position;
+};
+
+enum
+{
+  PROP_0,
+  PROP_DEVICE_INFO,
+};
+
+static void gst_asio_ring_buffer_dispose (GObject * object);
+
+static gboolean gst_asio_ring_buffer_open_device (GstAudioRingBuffer * buf);
+static gboolean gst_asio_ring_buffer_close_device (GstAudioRingBuffer * buf);
+static gboolean gst_asio_ring_buffer_acquire (GstAudioRingBuffer * buf,
+    GstAudioRingBufferSpec * spec);
+static gboolean gst_asio_ring_buffer_release (GstAudioRingBuffer * buf);
+static gboolean gst_asio_ring_buffer_start (GstAudioRingBuffer * buf);
+static gboolean gst_asio_ring_buffer_stop (GstAudioRingBuffer * buf);
+static guint gst_asio_ring_buffer_delay (GstAudioRingBuffer * buf);
+
+static gboolean gst_asio_buffer_switch_cb (GstAsioObject * obj,
+    glong index, ASIOBufferInfo * infos, guint num_infos,
+    ASIOChannelInfo * input_channel_infos,
+    ASIOChannelInfo * output_channel_infos,
+    ASIOSampleRate sample_rate, glong buffer_size, gpointer user_data);
+
+#define gst_asio_ring_buffer_parent_class parent_class
+G_DEFINE_TYPE (GstAsioRingBuffer, gst_asio_ring_buffer,
+    GST_TYPE_AUDIO_RING_BUFFER);
+
+static void
+gst_asio_ring_buffer_class_init (GstAsioRingBufferClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GstAudioRingBufferClass *ring_buffer_class =
+      GST_AUDIO_RING_BUFFER_CLASS (klass);
+
+  gobject_class->dispose = gst_asio_ring_buffer_dispose;
+
+  ring_buffer_class->open_device =
+      GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_open_device);
+  ring_buffer_class->close_device =
+      GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_close_device);
+  ring_buffer_class->acquire = GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_acquire);
+  ring_buffer_class->release = GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_release);
+  ring_buffer_class->start = GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_start);
+  ring_buffer_class->resume = GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_start);
+  ring_buffer_class->stop = GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_stop);
+  ring_buffer_class->delay = GST_DEBUG_FUNCPTR (gst_asio_ring_buffer_delay);
+
+  GST_DEBUG_CATEGORY_INIT (gst_asio_ring_buffer_debug,
+      "asioringbuffer", 0, "asioringbuffer");
+}
+
+static void
+gst_asio_ring_buffer_init (GstAsioRingBuffer * self)
+{
+}
+
+static void
+gst_asio_ring_buffer_dispose (GObject * object)
+{
+  GstAsioRingBuffer *self = GST_ASIO_RING_BUFFER (object);
+
+  gst_clear_object (&self->asio_object);
+  g_clear_pointer (&self->channel_indices, g_free);
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static gboolean
+gst_asio_ring_buffer_open_device (GstAudioRingBuffer * buf)
+{
+  GstAsioRingBuffer *self = GST_ASIO_RING_BUFFER (buf);
+
+  GST_DEBUG_OBJECT (self, "Open");
+
+  return TRUE;
+}
+
+static gboolean
+gst_asio_ring_buffer_close_device (GstAudioRingBuffer * buf)
+{
+  GstAsioRingBuffer *self = GST_ASIO_RING_BUFFER (buf);
+
+  GST_DEBUG_OBJECT (self, "Close");
+
+  return TRUE;
+}
+
+#define PACK_ASIO_64(v) ((v).lo | ((guint64)((v).hi) << 32))
+
+static gboolean
+gst_asio_buffer_switch_cb (GstAsioObject * obj, glong index,
+    ASIOBufferInfo * infos, guint num_infos,
+    ASIOChannelInfo * input_channel_infos,
+    ASIOChannelInfo * output_channel_infos,
+    ASIOSampleRate sample_rate, glong buffer_size,
+    ASIOTime * time_info, gpointer user_data)
+{
+  GstAsioRingBuffer *self = (GstAsioRingBuffer *) user_data;
+  GstAudioRingBuffer *ringbuffer = GST_AUDIO_RING_BUFFER_CAST (self);
+  gint segment;
+  guint8 *readptr;
+  gint len;
+  guint i, j;
+  guint num_channels = 0;
+  guint bps = GST_AUDIO_INFO_WIDTH (&ringbuffer->spec.info) >> 3;
+
+  g_assert (index == 0 || index == 1);
+  g_assert (num_infos >= self->num_channels);
+
+  GST_TRACE_OBJECT (self, "Buffer Switch callback, index %ld", index);
+
+  if (!gst_audio_ring_buffer_prepare_read (ringbuffer,
+          &segment, &readptr, &len)) {
+    GST_WARNING_OBJECT (self, "No segment available");
+    return TRUE;
+  }
+
+  GST_TRACE_OBJECT (self, "segment %d, length %d", segment, len);
+
+  /* Check missing frames */
+  if (self->type == GST_ASIO_DEVICE_CLASS_CAPTURE) {
+    if (self->is_first) {
+      if (time_info) {
+        self->expected_sample_position =
+            PACK_ASIO_64 (time_info->timeInfo.samplePosition) + buffer_size;
+        self->trace_sample_position = TRUE;
+      } else {
+        GST_WARNING_OBJECT (self, "ASIOTime is not available");
+        self->trace_sample_position = FALSE;
+      }
+
+      self->is_first = FALSE;
+    } else if (self->trace_sample_position) {
+      if (!time_info) {
+        GST_WARNING_OBJECT (self, "ASIOTime is not available");
+        self->trace_sample_position = FALSE;
+      } else {
+        guint64 sample_position =
+            PACK_ASIO_64 (time_info->timeInfo.samplePosition);
+        if (self->expected_sample_position < sample_position) {
+          guint64 gap_frames = sample_position - self->expected_sample_position;
+          gint gap_size = gap_frames * bps;
+
+          GST_WARNING_OBJECT (self, "%" G_GUINT64_FORMAT " frames are missing");
+
+          while (gap_size >= len) {
+            gst_audio_format_info_fill_silence (ringbuffer->spec.info.finfo,
+                readptr, len);
+            gst_audio_ring_buffer_advance (ringbuffer, 1);
+
+            gst_audio_ring_buffer_prepare_read (ringbuffer,
+                &segment, &readptr, &len);
+
+            gap_size -= len;
+          }
+        }
+
+        self->expected_sample_position = sample_position + buffer_size;
+        GST_TRACE_OBJECT (self, "Sample Position %" G_GUINT64_FORMAT
+            ", next: %" G_GUINT64_FORMAT, sample_position,
+            self->expected_sample_position);
+      }
+    }
+  }
+
+  /* Given @infos might contain more channel data, pick channels what we want to
+   * read */
+  for (i = 0; i < num_infos; i++) {
+    ASIOBufferInfo *info = &infos[i];
+
+    if (self->type == GST_ASIO_DEVICE_CLASS_CAPTURE) {
+      if (!info->isInput)
+        continue;
+    } else {
+      if (info->isInput)
+        continue;
+    }
+
+    for (j = 0; j < self->num_channels; j++) {
+      if (self->channel_indices[j] != info->channelNum)
+        continue;
+
+      g_assert (num_channels < self->num_channels);
+      self->infos[num_channels++] = info;
+      break;
+    }
+  }
+
+  if (num_channels < self->num_channels) {
+    GST_ERROR_OBJECT (self, "Too small number of channel %d (expected %d)",
+        num_channels, self->num_channels);
+  } else {
+    if (self->type == GST_ASIO_DEVICE_CLASS_CAPTURE ||
+        self->type == GST_ASIO_DEVICE_CLASS_LOOPBACK_CAPTURE) {
+      if (num_channels == 1) {
+        memcpy (readptr, self->infos[0]->buffers[index], len);
+      } else {
+        guint gst_offset = 0, asio_offset = 0;
+
+        /* Interleaves audio */
+        while (gst_offset < len) {
+          for (i = 0; i < num_channels; i++) {
+            ASIOBufferInfo *info = self->infos[i];
+
+            memcpy (readptr + gst_offset,
+                ((guint8 *) info->buffers[index]) + asio_offset, bps);
+
+            gst_offset += bps;
+          }
+          asio_offset += bps;
+        }
+      }
+    } else {
+      if (num_channels == 1) {
+        memcpy (self->infos[0]->buffers[index], readptr, len);
+      } else {
+        guint gst_offset = 0, asio_offset = 0;
+
+        /* Interleaves audio */
+        while (gst_offset < len) {
+          for (i = 0; i < num_channels; i++) {
+            ASIOBufferInfo *info = self->infos[i];
+
+            memcpy (((guint8 *) info->buffers[index]) + asio_offset,
+                readptr + gst_offset, bps);
+
+            gst_offset += bps;
+          }
+          asio_offset += bps;
+        }
+      }
+    }
+  }
+
+  if (self->type == GST_ASIO_DEVICE_CLASS_RENDER)
+    gst_audio_ring_buffer_clear (ringbuffer, segment);
+  gst_audio_ring_buffer_advance (ringbuffer, 1);
+
+  return TRUE;
+}
+
+static gboolean
+gst_asio_ring_buffer_acquire (GstAudioRingBuffer * buf,
+    GstAudioRingBufferSpec * spec)
+{
+  GstAsioRingBuffer *self = GST_ASIO_RING_BUFFER (buf);
+
+  if (!self->asio_object) {
+    GST_ERROR_OBJECT (self, "No configured ASIO object");
+    return FALSE;
+  }
+
+  if (!self->channel_indices || self->num_channels == 0) {
+    GST_ERROR_OBJECT (self, "No configured channels");
+    return FALSE;
+  }
+
+  if (!gst_asio_object_set_sample_rate (self->asio_object,
+          GST_AUDIO_INFO_RATE (&spec->info))) {
+    GST_ERROR_OBJECT (self, "Failed to set sample rate");
+    return FALSE;
+  }
+
+  spec->segsize = self->buffer_size *
+      (GST_AUDIO_INFO_WIDTH (&spec->info) >> 3) *
+      GST_AUDIO_INFO_CHANNELS (&spec->info);
+  spec->segtotal = 2;
+
+  buf->size = spec->segtotal * spec->segsize;
+  buf->memory = (guint8 *) g_malloc (buf->size);
+  gst_audio_format_info_fill_silence (buf->spec.info.finfo,
+      buf->memory, buf->size);
+
+  return TRUE;
+}
+
+static gboolean
+gst_asio_ring_buffer_release (GstAudioRingBuffer * buf)
+{
+  GST_DEBUG_OBJECT (buf, "Release");
+
+  g_clear_pointer (&buf->memory, g_free);
+
+  return TRUE;
+}
+
+static gboolean
+gst_asio_ring_buffer_start (GstAudioRingBuffer * buf)
+{
+  GstAsioRingBuffer *self = GST_ASIO_RING_BUFFER (buf);
+  GstAsioObjectCallbacks callbacks;
+
+  GST_DEBUG_OBJECT (buf, "Start");
+
+  callbacks.buffer_switch = gst_asio_buffer_switch_cb;
+  callbacks.user_data = self;
+
+  self->is_first = TRUE;
+  self->expected_sample_position = 0;
+
+  if (!gst_asio_object_install_callback (self->asio_object, self->type,
+          &callbacks, &self->callback_id)) {
+    GST_ERROR_OBJECT (self, "Failed to install callback");
+    return FALSE;
+  }
+
+  self->callback_installed = TRUE;
+
+  if (!gst_asio_object_start (self->asio_object)) {
+    GST_ERROR_OBJECT (self, "Failed to start");
+
+    gst_asio_ring_buffer_stop (buf);
+
+    return FALSE;
+  }
+
+  self->running = TRUE;
+
+  return TRUE;
+}
+
+static gboolean
+gst_asio_ring_buffer_stop (GstAudioRingBuffer * buf)
+{
+  GstAsioRingBuffer *self = GST_ASIO_RING_BUFFER (buf);
+
+  GST_DEBUG_OBJECT (buf, "Stop");
+
+  self->running = FALSE;
+
+  if (!self->asio_object)
+    return TRUE;
+
+  if (self->callback_installed)
+    gst_asio_object_uninstall_callback (self->asio_object, self->callback_id);
+
+  self->callback_installed = FALSE;
+  self->callback_id = 0;
+  self->is_first = TRUE;
+  self->expected_sample_position = 0;
+
+  return TRUE;
+}
+
+static guint
+gst_asio_ring_buffer_delay (GstAudioRingBuffer * buf)
+{
+  /* FIXME: impl. */
+
+  return 0;
+}
+
+GstAsioRingBuffer *
+gst_asio_ring_buffer_new (GstAsioObject * object, GstAsioDeviceClassType type,
+    const gchar * name)
+{
+  GstAsioRingBuffer *self;
+
+  g_return_val_if_fail (GST_IS_ASIO_OBJECT (object), nullptr);
+
+  self =
+      (GstAsioRingBuffer *) g_object_new (GST_TYPE_ASIO_RING_BUFFER,
+      "name", name, nullptr);
+  g_assert (self);
+
+  self->type = type;
+  self->asio_object = (GstAsioObject *) gst_object_ref (object);
+
+  return self;
+}
+
+gboolean
+gst_asio_ring_buffer_configure (GstAsioRingBuffer * buf,
+    guint * channel_indices, guint num_channles, guint preferred_buffer_size)
+{
+  g_return_val_if_fail (GST_IS_ASIO_RING_BUFFER (buf), FALSE);
+  g_return_val_if_fail (buf->asio_object != nullptr, FALSE);
+  g_return_val_if_fail (num_channles > 0, FALSE);
+
+  GST_DEBUG_OBJECT (buf, "Configure");
+
+  buf->buffer_size = preferred_buffer_size;
+
+  if (!gst_asio_object_create_buffers (buf->asio_object, buf->type,
+          channel_indices, num_channles, &buf->buffer_size)) {
+    GST_ERROR_OBJECT (buf, "Failed to configure");
+
+    g_clear_pointer (&buf->channel_indices, g_free);
+    buf->num_channels = 0;
+
+    return FALSE;
+  }
+
+  GST_DEBUG_OBJECT (buf, "configured buffer size: %d", buf->buffer_size);
+
+  g_free (buf->channel_indices);
+  buf->channel_indices = g_new0 (guint, num_channles);
+
+  for (guint i = 0; i < num_channles; i++)
+    buf->channel_indices[i] = channel_indices[i];
+
+  buf->num_channels = num_channles;
+
+  g_clear_pointer (&buf->infos, g_free);
+  buf->infos = g_new0 (ASIOBufferInfo *, num_channles);
+
+  return TRUE;
+}
+
+GstCaps *
+gst_asio_ring_buffer_get_caps (GstAsioRingBuffer * buf)
+{
+  g_return_val_if_fail (GST_IS_ASIO_RING_BUFFER (buf), nullptr);
+  g_assert (buf->asio_object != nullptr);
+
+  return gst_asio_object_get_caps (buf->asio_object,
+      buf->type, buf->num_channels, buf->num_channels);
+}
diff --git a/sys/asio/gstasioringbuffer.h b/sys/asio/gstasioringbuffer.h
new file mode 100644 (file)
index 0000000..338791c
--- /dev/null
@@ -0,0 +1,47 @@
+/* GStreamer
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_ASIO_RING_BUFFER_H__
+#define __GST_ASIO_RING_BUFFER_H__
+
+#include <gst/gst.h>
+#include <gst/audio/audio.h>
+#include "gstasioutils.h"
+#include "gstasioobject.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_ASIO_RING_BUFFER (gst_asio_ring_buffer_get_type())
+G_DECLARE_FINAL_TYPE (GstAsioRingBuffer, gst_asio_ring_buffer,
+    GST, ASIO_RING_BUFFER, GstAudioRingBuffer);
+
+GstAsioRingBuffer * gst_asio_ring_buffer_new (GstAsioObject * object,
+                                              GstAsioDeviceClassType type,
+                                              const gchar * name);
+
+gboolean            gst_asio_ring_buffer_configure (GstAsioRingBuffer * buf,
+                                                    guint * channel_indices,
+                                                    guint num_channles,
+                                                    guint preferred_buffer_size);
+
+GstCaps *           gst_asio_ring_buffer_get_caps (GstAsioRingBuffer * buf);
+
+G_END_DECLS
+
+#endif /* __GST_ASIO_RING_BUFFER_H__ */
\ No newline at end of file
diff --git a/sys/asio/gstasiosink.cpp b/sys/asio/gstasiosink.cpp
new file mode 100644 (file)
index 0000000..492e8fb
--- /dev/null
@@ -0,0 +1,361 @@
+/* GStreamer
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gstasiosink.h"
+#include "gstasioobject.h"
+#include "gstasioringbuffer.h"
+#include <atlconv.h>
+#include <string.h>
+#include <set>
+
+GST_DEBUG_CATEGORY_STATIC (gst_asio_sink_debug);
+#define GST_CAT_DEFAULT gst_asio_sink_debug
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (GST_ASIO_STATIC_CAPS));
+
+enum
+{
+  PROP_0,
+  PROP_DEVICE_CLSID,
+  PROP_OUTPUT_CHANNELS,
+  PROP_BUFFER_SIZE,
+  PROP_OCCUPY_ALL_CHANNELS,
+};
+
+#define DEFAULT_BUFFER_SIZE 0
+#define DEFAULT_OCCUPY_ALL_CHANNELS TRUE
+
+struct _GstAsioSink
+{
+  GstAudioSink parent;
+
+  /* properties */
+  gchar *device_clsid;
+  gchar *output_channels;
+  guint buffer_size;
+  gboolean occupy_all_channels;
+};
+
+static void gst_asio_sink_finalize (GObject * object);
+static void gst_asio_sink_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gst_asio_sink_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+static GstCaps *gst_asio_sink_get_caps (GstBaseSink * sink, GstCaps * filter);
+
+static GstAudioRingBuffer *gst_asio_sink_create_ringbuffer (GstAudioBaseSink *
+    sink);
+
+#define gst_asio_sink_parent_class parent_class
+G_DEFINE_TYPE (GstAsioSink, gst_asio_sink, GST_TYPE_AUDIO_BASE_SINK);
+
+static void
+gst_asio_sink_class_init (GstAsioSinkClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+  GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS (klass);
+  GstAudioBaseSinkClass *audiobasesink_class =
+      GST_AUDIO_BASE_SINK_CLASS (klass);
+
+  gobject_class->finalize = gst_asio_sink_finalize;
+  gobject_class->set_property = gst_asio_sink_set_property;
+  gobject_class->get_property = gst_asio_sink_get_property;
+
+  g_object_class_install_property (gobject_class, PROP_DEVICE_CLSID,
+      g_param_spec_string ("device-clsid", "Device CLSID",
+          "ASIO device CLSID as a string", NULL,
+          (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
+              G_PARAM_STATIC_STRINGS)));
+  g_object_class_install_property (gobject_class, PROP_OUTPUT_CHANNELS,
+      g_param_spec_string ("output-channels", "Output Channels",
+          "Comma-separated list of ASIO channels to output", NULL,
+          (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
+              G_PARAM_STATIC_STRINGS)));
+  g_object_class_install_property (gobject_class, PROP_BUFFER_SIZE,
+      g_param_spec_uint ("buffer-size", "Buffer Size",
+          "Preferred buffer size (0 for default)",
+          0, G_MAXINT32, DEFAULT_BUFFER_SIZE,
+          (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
+              G_PARAM_STATIC_STRINGS)));
+  g_object_class_install_property (gobject_class, PROP_OCCUPY_ALL_CHANNELS,
+      g_param_spec_boolean ("occupy-all-channels",
+          "Occupy All Channles",
+          "When enabled, ASIO device will allocate resources for all in/output "
+          "channles",
+          DEFAULT_OCCUPY_ALL_CHANNELS,
+          (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
+              G_PARAM_STATIC_STRINGS)));
+
+  gst_element_class_add_static_pad_template (element_class, &sink_template);
+  gst_element_class_set_static_metadata (element_class, "AsioSink",
+      "Source/Audio/Hardware",
+      "Stream audio from an audio capture device through ASIO",
+      "Seungha Yang <seungha@centricular.com>");
+
+  basesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_asio_sink_get_caps);
+
+  audiobasesink_class->create_ringbuffer =
+      GST_DEBUG_FUNCPTR (gst_asio_sink_create_ringbuffer);
+
+  GST_DEBUG_CATEGORY_INIT (gst_asio_sink_debug, "asiosink", 0, "asiosink");
+}
+
+static void
+gst_asio_sink_init (GstAsioSink * self)
+{
+  self->buffer_size = DEFAULT_BUFFER_SIZE;
+  self->occupy_all_channels = DEFAULT_OCCUPY_ALL_CHANNELS;
+}
+
+static void
+gst_asio_sink_finalize (GObject * object)
+{
+  GstAsioSink *self = GST_ASIO_SINK (object);
+
+  g_free (self->device_clsid);
+  g_free (self->output_channels);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_asio_sink_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstAsioSink *self = GST_ASIO_SINK (object);
+
+  switch (prop_id) {
+    case PROP_DEVICE_CLSID:
+      g_free (self->device_clsid);
+      self->device_clsid = g_value_dup_string (value);
+      break;
+    case PROP_OUTPUT_CHANNELS:
+      g_free (self->output_channels);
+      self->output_channels = g_value_dup_string (value);
+      break;
+    case PROP_BUFFER_SIZE:
+      self->buffer_size = g_value_get_uint (value);
+      break;
+    case PROP_OCCUPY_ALL_CHANNELS:
+      self->occupy_all_channels = g_value_get_boolean (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_asio_sink_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstAsioSink *self = GST_ASIO_SINK (object);
+
+  switch (prop_id) {
+    case PROP_DEVICE_CLSID:
+      g_value_set_string (value, self->device_clsid);
+      break;
+    case PROP_OUTPUT_CHANNELS:
+      g_value_set_string (value, self->output_channels);
+      break;
+    case PROP_BUFFER_SIZE:
+      g_value_set_uint (value, self->buffer_size);
+      break;
+    case PROP_OCCUPY_ALL_CHANNELS:
+      g_value_set_boolean (value, self->occupy_all_channels);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static GstCaps *
+gst_asio_sink_get_caps (GstBaseSink * sink, GstCaps * filter)
+{
+  GstAudioBaseSink *asink = GST_AUDIO_BASE_SINK (sink);
+  GstAsioSink *self = GST_ASIO_SINK (sink);
+  GstCaps *caps = nullptr;
+
+  if (asink->ringbuffer)
+    caps =
+        gst_asio_ring_buffer_get_caps (GST_ASIO_RING_BUFFER
+        (asink->ringbuffer));
+
+  if (!caps)
+    caps = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (sink));
+
+  if (filter) {
+    GstCaps *filtered =
+        gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
+    gst_caps_unref (caps);
+    caps = filtered;
+  }
+
+  GST_DEBUG_OBJECT (self, "returning caps %" GST_PTR_FORMAT, caps);
+
+  return caps;
+}
+
+static GstAudioRingBuffer *
+gst_asio_sink_create_ringbuffer (GstAudioBaseSink * sink)
+{
+  GstAsioSink *self = GST_ASIO_SINK (sink);
+  GstAsioRingBuffer *ringbuffer = nullptr;
+  HRESULT hr;
+  CLSID clsid = GUID_NULL;
+  GList *device_infos = nullptr;
+  GstAsioDeviceInfo *info = nullptr;
+  GstAsioObject *asio_object = nullptr;
+  glong max_input_ch = 0;
+  glong max_output_ch = 0;
+  guint *channel_indices = nullptr;
+  guint num_capture_channels = 0;
+  std::set < guint > channel_list;
+  guint i;
+  gchar *ringbuffer_name;
+
+  USES_CONVERSION;
+
+  GST_DEBUG_OBJECT (self, "Create ringbuffer");
+
+  if (gst_asio_enum (&device_infos) == 0) {
+    GST_WARNING_OBJECT (self, "No available ASIO devices");
+    return nullptr;
+  }
+
+  if (self->device_clsid) {
+    hr = CLSIDFromString (A2COLE (self->device_clsid), &clsid);
+    if (FAILED (hr)) {
+      GST_WARNING_OBJECT (self, "Failed to convert %s to CLSID",
+          self->device_clsid);
+      clsid = GUID_NULL;
+    }
+  }
+
+  /* Pick the first device */
+  if (clsid == GUID_NULL) {
+    info = (GstAsioDeviceInfo *) device_infos->data;
+  } else {
+    /* Find matching device */
+    GList *iter;
+    for (iter = device_infos; iter; iter = g_list_next (iter)) {
+      GstAsioDeviceInfo *tmp = (GstAsioDeviceInfo *) iter->data;
+      if (tmp->clsid == clsid) {
+        info = tmp;
+        break;
+      }
+    }
+  }
+
+  if (!info) {
+    GST_WARNING_OBJECT (self, "Failed to find matching device");
+    goto out;
+  }
+
+  asio_object = gst_asio_object_new (info, self->occupy_all_channels);
+  if (!asio_object) {
+    GST_WARNING_OBJECT (self, "Failed to create ASIO object");
+    goto out;
+  }
+
+  /* Configure channels to use */
+  if (!gst_asio_object_get_max_num_channels (asio_object, &max_input_ch,
+          &max_output_ch) || max_input_ch <= 0) {
+    GST_WARNING_OBJECT (self, "No available input channels");
+    goto out;
+  }
+
+  /* Check if user requested specific channle(s) */
+  if (self->output_channels) {
+    gchar **ch;
+
+    ch = g_strsplit (self->output_channels, ",", 0);
+
+    num_capture_channels = g_strv_length (ch);
+    if (num_capture_channels > max_input_ch) {
+      GST_WARNING_OBJECT (self, "To many channels %d were requested",
+          num_capture_channels);
+    } else {
+      for (i = 0; i < num_capture_channels; i++) {
+        guint64 c = g_ascii_strtoull (ch[i], nullptr, 0);
+        if (c >= (guint64) max_input_ch) {
+          GST_WARNING_OBJECT (self, "Invalid channel index");
+          num_capture_channels = 0;
+          break;
+        }
+
+        channel_list.insert ((guint) c);
+      }
+    }
+
+    g_strfreev (ch);
+  }
+
+  channel_indices = (guint *) g_alloca (sizeof (guint) * max_input_ch);
+  if (channel_list.size () == 0) {
+    for (i = 0; i < max_input_ch; i++)
+      channel_indices[i] = i;
+
+    num_capture_channels = max_input_ch;
+  } else {
+    num_capture_channels = (guint) channel_list.size ();
+    i = 0;
+  for (auto iter:channel_list) {
+      channel_indices[i++] = iter;
+    }
+  }
+
+  ringbuffer_name = g_strdup_printf ("%s-asioringbuffer",
+      GST_OBJECT_NAME (sink));
+
+  ringbuffer =
+      (GstAsioRingBuffer *) gst_asio_ring_buffer_new (asio_object,
+      GST_ASIO_DEVICE_CLASS_RENDER, ringbuffer_name);
+  g_free (ringbuffer_name);
+
+  if (!ringbuffer) {
+    GST_WARNING_OBJECT (self, "Couldn't create ringbuffer object");
+    goto out;
+  }
+
+  if (!gst_asio_ring_buffer_configure (ringbuffer, channel_indices,
+          num_capture_channels, self->buffer_size)) {
+    GST_WARNING_OBJECT (self, "Failed to configure ringbuffer");
+    gst_clear_object (&ringbuffer);
+    goto out;
+  }
+
+out:
+  if (device_infos)
+    g_list_free_full (device_infos, (GDestroyNotify) gst_asio_device_info_free);
+
+  gst_clear_object (&asio_object);
+
+  return GST_AUDIO_RING_BUFFER_CAST (ringbuffer);
+}
diff --git a/sys/asio/gstasiosink.h b/sys/asio/gstasiosink.h
new file mode 100644 (file)
index 0000000..bffafb1
--- /dev/null
@@ -0,0 +1,34 @@
+/* GStreamer
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_ASIO_SINK_H__
+#define __GST_ASIO_SINK_H__
+
+#include <gst/gst.h>
+#include <gst/audio/audio.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_ASIO_SINK (gst_asio_sink_get_type ())
+G_DECLARE_FINAL_TYPE (GstAsioSink,
+    gst_asio_sink, GST, ASIO_SINK, GstAudioBaseSink);
+
+G_END_DECLS
+
+#endif /* __GST_ASIO_SINK_H__ */
diff --git a/sys/asio/gstasiosrc.cpp b/sys/asio/gstasiosrc.cpp
new file mode 100644 (file)
index 0000000..47bbad5
--- /dev/null
@@ -0,0 +1,375 @@
+/* GStreamer
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gstasiosrc.h"
+#include "gstasioobject.h"
+#include "gstasioringbuffer.h"
+#include <atlconv.h>
+#include <string.h>
+#include <set>
+
+GST_DEBUG_CATEGORY_STATIC (gst_asio_src_debug);
+#define GST_CAT_DEFAULT gst_asio_src_debug
+
+static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (GST_ASIO_STATIC_CAPS));
+
+enum
+{
+  PROP_0,
+  PROP_DEVICE_CLSID,
+  PROP_CAPTURE_CHANNELS,
+  PROP_BUFFER_SIZE,
+  PROP_OCCUPY_ALL_CHANNELS,
+  PROP_LOOPBACK,
+};
+
+#define DEFAULT_BUFFER_SIZE 0
+#define DEFAULT_OCCUPY_ALL_CHANNELS TRUE
+#define DEFAULT_LOOPBACK    FALSE
+
+struct _GstAsioSrc
+{
+  GstAudioSrc parent;
+
+  /* properties */
+  gchar *device_clsid;
+  gchar *capture_channles;
+  guint buffer_size;
+  gboolean occupy_all_channels;
+  gboolean loopback;
+};
+
+static void gst_asio_src_finalize (GObject * object);
+static void gst_asio_src_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gst_asio_src_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+static GstCaps *gst_asio_src_get_caps (GstBaseSrc * src, GstCaps * filter);
+
+static GstAudioRingBuffer *gst_asio_src_create_ringbuffer (GstAudioBaseSrc *
+    src);
+
+#define gst_asio_src_parent_class parent_class
+G_DEFINE_TYPE (GstAsioSrc, gst_asio_src, GST_TYPE_AUDIO_BASE_SRC);
+
+static void
+gst_asio_src_class_init (GstAsioSrcClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+  GstBaseSrcClass *basesrc_class = GST_BASE_SRC_CLASS (klass);
+  GstAudioBaseSrcClass *audiobasesrc_class = GST_AUDIO_BASE_SRC_CLASS (klass);
+
+  gobject_class->finalize = gst_asio_src_finalize;
+  gobject_class->set_property = gst_asio_src_set_property;
+  gobject_class->get_property = gst_asio_src_get_property;
+
+  g_object_class_install_property (gobject_class, PROP_DEVICE_CLSID,
+      g_param_spec_string ("device-clsid", "Device CLSID",
+          "ASIO device CLSID as a string", NULL,
+          (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
+              G_PARAM_STATIC_STRINGS)));
+  g_object_class_install_property (gobject_class, PROP_CAPTURE_CHANNELS,
+      g_param_spec_string ("input-channels", "Input Channels",
+          "Comma-separated list of ASIO channels to capture", NULL,
+          (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
+              G_PARAM_STATIC_STRINGS)));
+  g_object_class_install_property (gobject_class, PROP_BUFFER_SIZE,
+      g_param_spec_uint ("buffer-size", "Buffer Size",
+          "Preferred buffer size (0 for default)",
+          0, G_MAXINT32, DEFAULT_BUFFER_SIZE,
+          (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
+              G_PARAM_STATIC_STRINGS)));
+  g_object_class_install_property (gobject_class, PROP_OCCUPY_ALL_CHANNELS,
+      g_param_spec_boolean ("occupy-all-channels",
+          "Occupy All Channles",
+          "When enabled, ASIO device will allocate resources for all in/output "
+          "channles",
+          DEFAULT_OCCUPY_ALL_CHANNELS,
+          (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
+              G_PARAM_STATIC_STRINGS)));
+  g_object_class_install_property (gobject_class, PROP_LOOPBACK,
+      g_param_spec_boolean ("loopback", "Loopback recording",
+          "Open the sink device for loopback recording",
+          DEFAULT_LOOPBACK,
+          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
+  gst_element_class_add_static_pad_template (element_class, &src_template);
+  gst_element_class_set_static_metadata (element_class, "AsioSrc",
+      "Source/Audio/Hardware",
+      "Stream audio from an audio capture device through ASIO",
+      "Seungha Yang <seungha@centricular.com>");
+
+  basesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_asio_src_get_caps);
+
+  audiobasesrc_class->create_ringbuffer =
+      GST_DEBUG_FUNCPTR (gst_asio_src_create_ringbuffer);
+
+  GST_DEBUG_CATEGORY_INIT (gst_asio_src_debug, "asiosrc", 0, "asiosrc");
+}
+
+static void
+gst_asio_src_init (GstAsioSrc * self)
+{
+  self->buffer_size = DEFAULT_BUFFER_SIZE;
+  self->occupy_all_channels = DEFAULT_OCCUPY_ALL_CHANNELS;
+  self->loopback = DEFAULT_LOOPBACK;
+}
+
+static void
+gst_asio_src_finalize (GObject * object)
+{
+  GstAsioSrc *self = GST_ASIO_SRC (object);
+
+  g_free (self->device_clsid);
+  g_free (self->capture_channles);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_asio_src_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstAsioSrc *self = GST_ASIO_SRC (object);
+
+  switch (prop_id) {
+    case PROP_DEVICE_CLSID:
+      g_free (self->device_clsid);
+      self->device_clsid = g_value_dup_string (value);
+      break;
+    case PROP_CAPTURE_CHANNELS:
+      g_free (self->capture_channles);
+      self->capture_channles = g_value_dup_string (value);
+      break;
+    case PROP_BUFFER_SIZE:
+      self->buffer_size = g_value_get_uint (value);
+      break;
+    case PROP_OCCUPY_ALL_CHANNELS:
+      self->occupy_all_channels = g_value_get_boolean (value);
+      break;
+    case PROP_LOOPBACK:
+      self->loopback = g_value_get_boolean (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_asio_src_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstAsioSrc *self = GST_ASIO_SRC (object);
+
+  switch (prop_id) {
+    case PROP_DEVICE_CLSID:
+      g_value_set_string (value, self->device_clsid);
+      break;
+    case PROP_CAPTURE_CHANNELS:
+      g_value_set_string (value, self->capture_channles);
+      break;
+    case PROP_BUFFER_SIZE:
+      g_value_set_uint (value, self->buffer_size);
+      break;
+    case PROP_OCCUPY_ALL_CHANNELS:
+      g_value_set_boolean (value, self->occupy_all_channels);
+      break;
+    case PROP_LOOPBACK:
+      g_value_set_boolean (value, self->loopback);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static GstCaps *
+gst_asio_src_get_caps (GstBaseSrc * src, GstCaps * filter)
+{
+  GstAudioBaseSrc *asrc = GST_AUDIO_BASE_SRC (src);
+  GstAsioSrc *self = GST_ASIO_SRC (src);
+  GstCaps *caps = nullptr;
+
+  if (asrc->ringbuffer)
+    caps =
+        gst_asio_ring_buffer_get_caps (GST_ASIO_RING_BUFFER (asrc->ringbuffer));
+
+  if (!caps)
+    caps = gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (src));
+
+  if (filter) {
+    GstCaps *filtered =
+        gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
+    gst_caps_unref (caps);
+    caps = filtered;
+  }
+
+  GST_DEBUG_OBJECT (self, "returning caps %" GST_PTR_FORMAT, caps);
+
+  return caps;
+}
+
+static GstAudioRingBuffer *
+gst_asio_src_create_ringbuffer (GstAudioBaseSrc * src)
+{
+  GstAsioSrc *self = GST_ASIO_SRC (src);
+  GstAsioRingBuffer *ringbuffer = nullptr;
+  HRESULT hr;
+  CLSID clsid = GUID_NULL;
+  GList *device_infos = nullptr;
+  GstAsioDeviceInfo *info = nullptr;
+  GstAsioObject *asio_object = nullptr;
+  glong max_input_ch = 0;
+  glong max_output_ch = 0;
+  guint *channel_indices = nullptr;
+  guint num_capture_channels = 0;
+  std::set < guint > channel_list;
+  guint i;
+  gchar *ringbuffer_name;
+
+  USES_CONVERSION;
+
+  GST_DEBUG_OBJECT (self, "Create ringbuffer");
+
+  if (gst_asio_enum (&device_infos) == 0) {
+    GST_WARNING_OBJECT (self, "No available ASIO devices");
+    return nullptr;
+  }
+
+  if (self->device_clsid) {
+    hr = CLSIDFromString (A2COLE (self->device_clsid), &clsid);
+    if (FAILED (hr)) {
+      GST_WARNING_OBJECT (self, "Failed to convert %s to CLSID",
+          self->device_clsid);
+      clsid = GUID_NULL;
+    }
+  }
+
+  /* Pick the first device */
+  if (clsid == GUID_NULL) {
+    info = (GstAsioDeviceInfo *) device_infos->data;
+  } else {
+    /* Find matching device */
+    GList *iter;
+    for (iter = device_infos; iter; iter = g_list_next (iter)) {
+      GstAsioDeviceInfo *tmp = (GstAsioDeviceInfo *) iter->data;
+      if (tmp->clsid == clsid) {
+        info = tmp;
+        break;
+      }
+    }
+  }
+
+  if (!info) {
+    GST_WARNING_OBJECT (self, "Failed to find matching device");
+    goto out;
+  }
+
+  asio_object = gst_asio_object_new (info, self->occupy_all_channels);
+  if (!asio_object) {
+    GST_WARNING_OBJECT (self, "Failed to create ASIO object");
+    goto out;
+  }
+
+  /* Configure channels to use */
+  if (!gst_asio_object_get_max_num_channels (asio_object, &max_input_ch,
+          &max_output_ch) || max_input_ch <= 0) {
+    GST_WARNING_OBJECT (self, "No available input channels");
+    goto out;
+  }
+
+  /* Check if user requested specific channel(s) */
+  if (self->capture_channles) {
+    gchar **ch;
+
+    ch = g_strsplit (self->capture_channles, ",", 0);
+
+    num_capture_channels = g_strv_length (ch);
+    if (num_capture_channels > max_input_ch) {
+      GST_WARNING_OBJECT (self, "To many channels %d were requested",
+          num_capture_channels);
+    } else {
+      for (i = 0; i < num_capture_channels; i++) {
+        guint64 c = g_ascii_strtoull (ch[i], nullptr, 0);
+        if (c >= (guint64) max_input_ch) {
+          GST_WARNING_OBJECT (self, "Invalid channel index");
+          num_capture_channels = 0;
+          break;
+        }
+
+        channel_list.insert ((guint) c);
+      }
+    }
+
+    g_strfreev (ch);
+  }
+
+  channel_indices = (guint *) g_alloca (sizeof (guint) * max_input_ch);
+  if (channel_list.size () == 0) {
+    for (i = 0; i < max_input_ch; i++)
+      channel_indices[i] = i;
+
+    num_capture_channels = max_input_ch;
+  } else {
+    num_capture_channels = (guint) channel_list.size ();
+    i = 0;
+  for (auto iter:channel_list) {
+      channel_indices[i++] = iter;
+    }
+  }
+
+  ringbuffer_name = g_strdup_printf ("%s-asioringbuffer",
+      GST_OBJECT_NAME (src));
+
+  ringbuffer =
+      (GstAsioRingBuffer *) gst_asio_ring_buffer_new (asio_object,
+      self->loopback ? GST_ASIO_DEVICE_CLASS_LOOPBACK_CAPTURE :
+      GST_ASIO_DEVICE_CLASS_CAPTURE, ringbuffer_name);
+  g_free (ringbuffer_name);
+
+  if (!ringbuffer) {
+    GST_WARNING_OBJECT (self, "Couldn't create ringbuffer object");
+    goto out;
+  }
+
+  if (!gst_asio_ring_buffer_configure (ringbuffer, channel_indices,
+          num_capture_channels, self->buffer_size)) {
+    GST_WARNING_OBJECT (self, "Failed to configure ringbuffer");
+    gst_clear_object (&ringbuffer);
+    goto out;
+  }
+
+out:
+  if (device_infos)
+    g_list_free_full (device_infos, (GDestroyNotify) gst_asio_device_info_free);
+
+  gst_clear_object (&asio_object);
+
+  return GST_AUDIO_RING_BUFFER_CAST (ringbuffer);
+}
diff --git a/sys/asio/gstasiosrc.h b/sys/asio/gstasiosrc.h
new file mode 100644 (file)
index 0000000..704ddcb
--- /dev/null
@@ -0,0 +1,34 @@
+/* GStreamer
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_ASIO_SRC_H__
+#define __GST_ASIO_SRC_H__
+
+#include <gst/gst.h>
+#include <gst/audio/audio.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_ASIO_SRC (gst_asio_src_get_type ())
+G_DECLARE_FINAL_TYPE (GstAsioSrc,
+    gst_asio_src, GST, ASIO_SRC, GstAudioBaseSrc);
+
+G_END_DECLS
+
+#endif /* __GST_ASIO_SRC_H__ */
diff --git a/sys/asio/gstasioutils.cpp b/sys/asio/gstasioutils.cpp
new file mode 100644 (file)
index 0000000..687af9c
--- /dev/null
@@ -0,0 +1,282 @@
+/* GStreamer
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstasioutils.h"
+#include <windows.h>
+#include <string.h>
+#include <atlconv.h>
+
+static gboolean
+gst_asio_enum_check_class_root (GstAsioDeviceInfo * info, LPCWSTR clsid)
+{
+  LSTATUS status;
+  HKEY root_key = nullptr;
+  HKEY device_key = nullptr;
+  HKEY proc_server_key = nullptr;
+  DWORD type = REG_SZ;
+  CHAR data[256];
+  DWORD size = sizeof (data);
+  gboolean ret = FALSE;
+
+  status = RegOpenKeyExW (HKEY_CLASSES_ROOT, L"clsid", 0, KEY_READ, &root_key);
+  if (status != ERROR_SUCCESS)
+    return FALSE;
+
+  /* Read registry HKEY_CLASS_ROOT/CLSID/{device-clsid} */
+  status = RegOpenKeyExW (root_key, clsid, 0, KEY_READ, &device_key);
+  if (status != ERROR_SUCCESS)
+    goto done;
+
+  /* ThreadingModel describes COM apartment */
+  status = RegOpenKeyExW (device_key,
+      L"InprocServer32", 0, KEY_READ, &proc_server_key);
+  if (status != ERROR_SUCCESS)
+    goto done;
+
+  status = RegQueryValueExA (proc_server_key,
+      "ThreadingModel", nullptr, &type, (LPBYTE) data, &size);
+  if (status != ERROR_SUCCESS)
+    goto done;
+
+  if (g_ascii_strcasecmp (data, "Both") == 0 ||
+      g_ascii_strcasecmp (data, "Free") == 0) {
+    info->sta_model = FALSE;
+  } else {
+    info->sta_model = TRUE;
+  }
+
+  ret = TRUE;
+
+done:
+  if (proc_server_key)
+    RegCloseKey (proc_server_key);
+
+  if (device_key)
+    RegCloseKey (device_key);
+
+  if (root_key)
+    RegCloseKey (root_key);
+
+  return ret;
+}
+
+static GstAsioDeviceInfo *
+gst_asio_enum_new_device_info_from_reg (HKEY reg_key, LPWSTR key_name)
+{
+  LSTATUS status;
+  HKEY sub_key = nullptr;
+  WCHAR clsid_data[256];
+  WCHAR desc_data[256];
+  DWORD type = REG_SZ;
+  DWORD size = sizeof (clsid_data);
+  GstAsioDeviceInfo *ret = nullptr;
+  CLSID id;
+  HRESULT hr;
+
+  USES_CONVERSION;
+
+  status = RegOpenKeyExW (reg_key, key_name, 0, KEY_READ, &sub_key);
+  if (status != ERROR_SUCCESS)
+    return nullptr;
+
+  /* find CLSID value, used for CoCreateInstance */
+  status = RegQueryValueExW (sub_key,
+      L"clsid", 0, &type, (LPBYTE) clsid_data, &size);
+  if (status != ERROR_SUCCESS)
+    goto done;
+
+  hr = CLSIDFromString (W2COLE (clsid_data), &id);
+  if (FAILED (hr))
+    goto done;
+
+  ret = g_new0 (GstAsioDeviceInfo, 1);
+  ret->clsid = id;
+  ret->driver_name = g_utf16_to_utf8 ((gunichar2 *) key_name, -1,
+      nullptr, nullptr, nullptr);
+
+  /* human readable device description */
+  status = RegQueryValueExW (sub_key,
+      L"description", 0, &type, (LPBYTE) desc_data, &size);
+  if (status != ERROR_SUCCESS) {
+    GST_WARNING ("no description");
+    ret->driver_desc = g_strdup (ret->driver_name);
+  } else {
+    ret->driver_desc = g_utf16_to_utf8 ((gunichar2 *) desc_data, -1,
+        nullptr, nullptr, nullptr);
+  }
+
+  /* Check COM threading model */
+  if (!gst_asio_enum_check_class_root (ret, clsid_data)) {
+    gst_asio_device_info_free (ret);
+    ret = nullptr;
+  }
+
+done:
+  if (sub_key)
+    RegCloseKey (sub_key);
+
+  return ret;
+}
+
+guint
+gst_asio_enum (GList ** infos)
+{
+  GList *info_list = nullptr;
+  DWORD index = 0;
+  guint num_device = 0;
+  LSTATUS status;
+  HKEY reg_key = nullptr;
+  WCHAR key_name[512];
+
+  g_return_val_if_fail (infos != nullptr, 0);
+
+  status = RegOpenKeyExW (HKEY_LOCAL_MACHINE, L"software\\asio", 0,
+      KEY_READ, &reg_key);
+  while (status == ERROR_SUCCESS) {
+    GstAsioDeviceInfo *info;
+
+    status = RegEnumKeyW (reg_key, index, key_name, 512);
+    if (status != ERROR_SUCCESS)
+      break;
+
+    index++;
+    info = gst_asio_enum_new_device_info_from_reg (reg_key, key_name);
+    if (!info)
+      continue;
+
+    info_list = g_list_append (info_list, info);
+    num_device++;
+  }
+
+  if (reg_key)
+    RegCloseKey (reg_key);
+
+  *infos = info_list;
+
+  return num_device;
+}
+
+GstAsioDeviceInfo *
+gst_asio_device_info_copy (const GstAsioDeviceInfo * info)
+{
+  GstAsioDeviceInfo *new_info;
+
+  if (!info)
+    return nullptr;
+
+  new_info = g_new0 (GstAsioDeviceInfo, 1);
+
+  new_info->clsid = info->clsid;
+  new_info->sta_model = info->sta_model;
+  new_info->driver_name = g_strdup (info->driver_name);
+  new_info->driver_desc = g_strdup (info->driver_desc);
+
+  return new_info;
+}
+
+void
+gst_asio_device_info_free (GstAsioDeviceInfo * info)
+{
+  if (!info)
+    return;
+
+  g_free (info->driver_name);
+  g_free (info->driver_desc);
+
+  g_free (info);
+}
+
+GstAudioFormat
+gst_asio_sample_type_to_gst (ASIOSampleType type)
+{
+  GstAudioFormat fmt;
+
+  switch (type) {
+      /*~~ MSB means big endian ~~ */
+    case ASIOSTInt16MSB:
+      fmt = GST_AUDIO_FORMAT_S16BE;
+      break;
+      /* FIXME: also used for 20 bits packed in 24 bits, how do we detect that? */
+    case ASIOSTInt24MSB:
+      fmt = GST_AUDIO_FORMAT_S24BE;
+      break;
+    case ASIOSTInt32MSB:
+      fmt = GST_AUDIO_FORMAT_S32BE;
+      break;
+    case ASIOSTFloat32MSB:
+      fmt = GST_AUDIO_FORMAT_F32BE;
+      break;
+    case ASIOSTFloat64MSB:
+      fmt = GST_AUDIO_FORMAT_F64BE;
+      break;
+      /* All these are aligned to a different boundary than the packing, not sure
+       * how to handle it, let's try the normal S32BE format */
+    case ASIOSTInt32MSB16:
+    case ASIOSTInt32MSB18:
+    case ASIOSTInt32MSB20:
+    case ASIOSTInt32MSB24:
+      fmt = GST_AUDIO_FORMAT_S32BE;
+      break;
+
+      /*~~ LSB means little endian ~~ */
+    case ASIOSTInt16LSB:
+      fmt = GST_AUDIO_FORMAT_S16LE;
+      break;
+      /* FIXME: also used for 20 bits packed in 24 bits, how do we detect that? */
+    case ASIOSTInt24LSB:
+      fmt = GST_AUDIO_FORMAT_S24LE;
+      break;
+    case ASIOSTInt32LSB:
+      fmt = GST_AUDIO_FORMAT_S32LE;
+      break;
+    case ASIOSTFloat32LSB:
+      fmt = GST_AUDIO_FORMAT_F32LE;
+      break;
+    case ASIOSTFloat64LSB:
+      fmt = GST_AUDIO_FORMAT_F64LE;
+      break;
+      /* All these are aligned to a different boundary than the packing, not sure
+       * how to handle it, let's try the normal S32LE format */
+    case ASIOSTInt32LSB16:
+    case ASIOSTInt32LSB18:
+    case ASIOSTInt32LSB20:
+    case ASIOSTInt32LSB24:
+      GST_WARNING ("weird alignment %ld, trying S32LE", type);
+      fmt = GST_AUDIO_FORMAT_S32LE;
+      break;
+
+      /*~~ ASIO DSD formats are don't have gstreamer mappings ~~ */
+    case ASIOSTDSDInt8LSB1:
+    case ASIOSTDSDInt8MSB1:
+    case ASIOSTDSDInt8NER8:
+      GST_ERROR ("ASIO DSD formats are not supported");
+      fmt = GST_AUDIO_FORMAT_UNKNOWN;
+      break;
+    default:
+      GST_ERROR ("Unknown asio sample type %ld", type);
+      fmt = GST_AUDIO_FORMAT_UNKNOWN;
+      break;
+  }
+
+  return fmt;
+}
diff --git a/sys/asio/gstasioutils.h b/sys/asio/gstasioutils.h
new file mode 100644 (file)
index 0000000..ef159c7
--- /dev/null
@@ -0,0 +1,55 @@
+/* GStreamer
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_ASIO_DEVICE_ENUM_H__
+#define __GST_ASIO_DEVICE_ENUM_H__
+
+#include <gst/gst.h>
+#include <gst/audio/audio.h>
+#include <windows.h>
+#include <asiosys.h>
+#include <asio.h>
+
+G_BEGIN_DECLS
+
+#define GST_ASIO_STATIC_CAPS "audio/x-raw, " \
+        "format = (string) " GST_AUDIO_FORMATS_ALL ", " \
+        "layout = (string) interleaved, " \
+        "rate = " GST_AUDIO_RATE_RANGE ", " \
+        "channels = " GST_AUDIO_CHANNELS_RANGE
+
+typedef struct
+{
+  CLSID clsid;
+  gboolean sta_model;
+  gchar *driver_name;
+  gchar *driver_desc;
+} GstAsioDeviceInfo;
+
+guint               gst_asio_enum (GList ** infos);
+
+GstAsioDeviceInfo * gst_asio_device_info_copy (const GstAsioDeviceInfo * info);
+
+void                gst_asio_device_info_free (GstAsioDeviceInfo * info);
+
+GstAudioFormat      gst_asio_sample_type_to_gst (ASIOSampleType type);
+
+G_END_DECLS
+
+#endif /* __GST_ASIO_DEVICE_ENUM_H__ */
\ No newline at end of file
diff --git a/sys/asio/meson.build b/sys/asio/meson.build
new file mode 100644 (file)
index 0000000..3006d26
--- /dev/null
@@ -0,0 +1,84 @@
+asio_sources = [
+  'gstasiodeviceprovider.cpp',
+  'gstasioobject.cpp',
+  'gstasioringbuffer.cpp',
+  'gstasiosink.cpp',
+  'gstasiosrc.cpp',
+  'gstasioutils.cpp',
+  'plugin.c',
+]
+
+asio_option = get_option('asio')
+if asio_option.disabled() or host_system != 'windows'
+  subdir_done()
+endif
+
+# FIXME: non-msvc is not tested, and unlikely supported yet because of
+# tool-chain issue
+if cxx.get_id() != 'msvc'
+  if asio_option.enabled()
+    error('asio plugin can only be built with MSVC')
+  else
+    subdir_done ()
+  endif
+endif
+
+winapi_desktop = cxx.compiles('''#include <winapifamily.h>
+    #if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+    #error "not win32"
+    #endif''',
+    name: 'building for Win32')
+
+if not winapi_desktop
+  if asio_option.enabled()
+    error('asio plugin requires WINAPI_PARTITION_DESKTOP')
+  else
+    subdir_done ()
+  endif
+endif
+
+avrt_lib = cc.find_library('avrt', required: asio_option)
+if not avrt_lib.found()
+  subdir_done ()
+endif
+
+winmm_lib = cc.find_library('winmm', required: asio_option)
+if not winmm_lib.found()
+  subdir_done ()
+endif
+
+# Checking SDK headers. User should install ASIO sdk on system, and
+# this plugin requires asio.h, asiosys.h and iasiodrv.h headers
+asio_sdk_root = get_option ('asio-sdk-path')
+if asio_sdk_root == ''
+  if asio_option.enabled()
+    error('asio sdk path is needed, pass with -Dasio-sdk-path=C:/path/to/sdk')
+  else
+    subdir_done ()
+  endif
+endif
+
+asio_inc_dir = include_directories(join_paths(asio_sdk_root, 'common'), is_system : true)
+has_asio_header = cxx.has_header('asio.h', include_directories: asio_inc_dir)
+has_asiosys_header = cxx.has_header('asiosys.h', include_directories: asio_inc_dir)
+has_iasiodrv_header = cxx.has_header('iasiodrv.h', include_directories: asio_inc_dir)
+if not has_asio_header or not has_asiosys_header or not has_iasiodrv_header
+  if asio_option.enabled()
+    error('Failed to find required SDK header(s)')
+  else
+    subdir_done ()
+  endif
+endif
+
+asio_deps = [gstaudio_dep, avrt_lib, winmm_lib]
+
+gstasio = library('gstasio',
+    asio_sources,
+    include_directories : [configinc, asio_inc_dir],
+    dependencies : asio_deps,
+    c_args : gst_plugins_bad_args,
+    cpp_args : gst_plugins_bad_args,
+    install : true,
+    install_dir : plugins_install_dir)
+pkgconfig.generate(gstasio, install_dir : plugins_pkgconfig_install_dir)
+plugins += [gstasio]
\ No newline at end of file
diff --git a/sys/asio/plugin.c b/sys/asio/plugin.c
new file mode 100644 (file)
index 0000000..5581137
--- /dev/null
@@ -0,0 +1,48 @@
+/* GStreamer
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstasiodeviceprovider.h"
+#include "gstasiosrc.h"
+#include "gstasiosink.h"
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  GstRank rank = GST_RANK_SECONDARY;
+
+  if (!gst_element_register (plugin, "asiosrc", rank, GST_TYPE_ASIO_SRC))
+    return FALSE;
+  if (!gst_element_register (plugin, "asiosink", rank, GST_TYPE_ASIO_SINK))
+    return FALSE;
+  if (!gst_device_provider_register (plugin, "asiodeviceprovider",
+          rank, GST_TYPE_ASIO_DEVICE_PROVIDER))
+    return FALSE;
+
+  return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    asio,
+    "Steinberg ASIO plugin",
+    plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
index cf39e57..f635077 100644 (file)
@@ -1,5 +1,6 @@
 subdir('androidmedia')
 subdir('applemedia')
+subdir('asio')
 subdir('bluez')
 subdir('d3d11')
 subdir('d3dvideosink')