mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-13 12:51:16 +00:00
375 lines
11 KiB
C++
375 lines
11 KiB
C++
/* 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);
|
|
}
|