summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--meson_options.txt2
-rw-r--r--sys/asio/gstasiodeviceprovider.cpp273
-rw-r--r--sys/asio/gstasiodeviceprovider.h37
-rw-r--r--sys/asio/gstasioobject.cpp1875
-rw-r--r--sys/asio/gstasioobject.h104
-rw-r--r--sys/asio/gstasioringbuffer.cpp473
-rw-r--r--sys/asio/gstasioringbuffer.h47
-rw-r--r--sys/asio/gstasiosink.cpp361
-rw-r--r--sys/asio/gstasiosink.h34
-rw-r--r--sys/asio/gstasiosrc.cpp375
-rw-r--r--sys/asio/gstasiosrc.h34
-rw-r--r--sys/asio/gstasioutils.cpp282
-rw-r--r--sys/asio/gstasioutils.h55
-rw-r--r--sys/asio/meson.build84
-rw-r--r--sys/asio/plugin.c48
-rw-r--r--sys/meson.build1
16 files changed, 4085 insertions, 0 deletions
diff --git a/meson_options.txt b/meson_options.txt
index 4a9778073..c24b3b3b0 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -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
index 000000000..4d1e9b0d0
--- /dev/null
+++ b/sys/asio/gstasiodeviceprovider.cpp
@@ -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
index 000000000..24126e808
--- /dev/null
+++ b/sys/asio/gstasiodeviceprovider.h
@@ -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
index 000000000..675ffc89a
--- /dev/null
+++ b/sys/asio/gstasioobject.cpp
@@ -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
index 000000000..7d80b7b99
--- /dev/null
+++ b/sys/asio/gstasioobject.h
@@ -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
index 000000000..449d54bb4
--- /dev/null
+++ b/sys/asio/gstasioringbuffer.cpp
@@ -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
index 000000000..338791c6f
--- /dev/null
+++ b/sys/asio/gstasioringbuffer.h
@@ -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
index 000000000..492e8fbb4
--- /dev/null
+++ b/sys/asio/gstasiosink.cpp
@@ -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
index 000000000..bffafb12a
--- /dev/null
+++ b/sys/asio/gstasiosink.h
@@ -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
index 000000000..47bbad5f0
--- /dev/null
+++ b/sys/asio/gstasiosrc.cpp
@@ -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
index 000000000..704ddcb7d
--- /dev/null
+++ b/sys/asio/gstasiosrc.h
@@ -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
index 000000000..687af9c94
--- /dev/null
+++ b/sys/asio/gstasioutils.cpp
@@ -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
index 000000000..ef159c7e0
--- /dev/null
+++ b/sys/asio/gstasioutils.h
@@ -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
index 000000000..3006d26ce
--- /dev/null
+++ b/sys/asio/meson.build
@@ -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
index 000000000..55811372c
--- /dev/null
+++ b/sys/asio/plugin.c
@@ -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)
diff --git a/sys/meson.build b/sys/meson.build
index cf39e5770..f63507737 100644
--- a/sys/meson.build
+++ b/sys/meson.build
@@ -1,5 +1,6 @@
subdir('androidmedia')
subdir('applemedia')
+subdir('asio')
subdir('bluez')
subdir('d3d11')
subdir('d3dvideosink')