Introduce Steinberg ASIO (Audio Streaming Input/Output) plugin

Adds a new plugin for ASIO devices.

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

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

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2309>
This commit is contained in:
Seungha Yang 2021-06-08 01:40:34 +09:00 committed by GStreamer Marge Bot
parent 4d1101d335
commit dc787434bc
16 changed files with 4085 additions and 0 deletions

View file

@ -81,6 +81,8 @@ option('aom', type : 'feature', value : 'auto', description : 'AOM AV1 video cod
option('avtp', type : 'feature', value : 'auto', description : 'Audio/Video Transport Protocol (AVTP) plugin')
option('androidmedia', type : 'feature', value : 'auto', description : 'Video capture and codec plugins for Android')
option('applemedia', type : 'feature', value : 'auto', description : 'Video capture and codec access plugins for macOS and iOS')
option('asio', type : 'feature', value : 'auto', description : 'Steinberg Audio Streaming Input Output (ASIO) plugin')
option('asio-sdk-path', type : 'string', value : '', description : 'Full path to Steinberg Audio Streaming Input Output (ASIO) SDK')
option('assrender', type : 'feature', value : 'auto', description : 'ASS/SSA subtitle renderer plugin')
option('bluez', type : 'feature', value : 'auto', description : 'Bluetooth audio A2DP/AVDTP sink, AVDTP source plugin')
option('bs2b', type : 'feature', value : 'auto', description : 'Bauer stereophonic-to-binaural audio plugin')

View file

@ -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;
}

View file

@ -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__ */

1875
sys/asio/gstasioobject.cpp Normal file

File diff suppressed because it is too large Load diff

104
sys/asio/gstasioobject.h Normal file
View file

@ -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__ */

View file

@ -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);
}

View file

@ -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__ */

361
sys/asio/gstasiosink.cpp Normal file
View file

@ -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);
}

34
sys/asio/gstasiosink.h Normal file
View file

@ -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__ */

375
sys/asio/gstasiosrc.cpp Normal file
View file

@ -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);
}

34
sys/asio/gstasiosrc.h Normal file
View file

@ -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__ */

282
sys/asio/gstasioutils.cpp Normal file
View file

@ -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;
}

55
sys/asio/gstasioutils.h Normal file
View file

@ -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__ */

84
sys/asio/meson.build Normal file
View file

@ -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]

48
sys/asio/plugin.c Normal file
View file

@ -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)

View file

@ -1,5 +1,6 @@
subdir('androidmedia')
subdir('applemedia')
subdir('asio')
subdir('bluez')
subdir('d3d11')
subdir('d3dvideosink')