• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23 
24 #include "gstasiosrc.h"
25 #include "gstasioobject.h"
26 #include "gstasioringbuffer.h"
27 #include <atlconv.h>
28 #include <string.h>
29 #include <set>
30 
31 GST_DEBUG_CATEGORY_STATIC (gst_asio_src_debug);
32 #define GST_CAT_DEFAULT gst_asio_src_debug
33 
34 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
35     GST_PAD_SRC,
36     GST_PAD_ALWAYS,
37     GST_STATIC_CAPS (GST_ASIO_STATIC_CAPS));
38 
39 enum
40 {
41   PROP_0,
42   PROP_DEVICE_CLSID,
43   PROP_CAPTURE_CHANNELS,
44   PROP_BUFFER_SIZE,
45   PROP_OCCUPY_ALL_CHANNELS,
46   PROP_LOOPBACK,
47 };
48 
49 #define DEFAULT_BUFFER_SIZE 0
50 #define DEFAULT_OCCUPY_ALL_CHANNELS TRUE
51 #define DEFAULT_LOOPBACK    FALSE
52 
53 struct _GstAsioSrc
54 {
55   GstAudioSrc parent;
56 
57   /* properties */
58   gchar *device_clsid;
59   gchar *capture_channles;
60   guint buffer_size;
61   gboolean occupy_all_channels;
62   gboolean loopback;
63 };
64 
65 static void gst_asio_src_finalize (GObject * object);
66 static void gst_asio_src_set_property (GObject * object, guint prop_id,
67     const GValue * value, GParamSpec * pspec);
68 static void gst_asio_src_get_property (GObject * object, guint prop_id,
69     GValue * value, GParamSpec * pspec);
70 
71 static GstCaps *gst_asio_src_get_caps (GstBaseSrc * src, GstCaps * filter);
72 
73 static GstAudioRingBuffer *gst_asio_src_create_ringbuffer (GstAudioBaseSrc *
74     src);
75 
76 #define gst_asio_src_parent_class parent_class
77 G_DEFINE_TYPE (GstAsioSrc, gst_asio_src, GST_TYPE_AUDIO_BASE_SRC);
78 
79 static void
gst_asio_src_class_init(GstAsioSrcClass * klass)80 gst_asio_src_class_init (GstAsioSrcClass * klass)
81 {
82   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
83   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
84   GstBaseSrcClass *basesrc_class = GST_BASE_SRC_CLASS (klass);
85   GstAudioBaseSrcClass *audiobasesrc_class = GST_AUDIO_BASE_SRC_CLASS (klass);
86 
87   gobject_class->finalize = gst_asio_src_finalize;
88   gobject_class->set_property = gst_asio_src_set_property;
89   gobject_class->get_property = gst_asio_src_get_property;
90 
91   g_object_class_install_property (gobject_class, PROP_DEVICE_CLSID,
92       g_param_spec_string ("device-clsid", "Device CLSID",
93           "ASIO device CLSID as a string", NULL,
94           (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
95               G_PARAM_STATIC_STRINGS)));
96   g_object_class_install_property (gobject_class, PROP_CAPTURE_CHANNELS,
97       g_param_spec_string ("input-channels", "Input Channels",
98           "Comma-separated list of ASIO channels to capture", NULL,
99           (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
100               G_PARAM_STATIC_STRINGS)));
101   g_object_class_install_property (gobject_class, PROP_BUFFER_SIZE,
102       g_param_spec_uint ("buffer-size", "Buffer Size",
103           "Preferred buffer size (0 for default)",
104           0, G_MAXINT32, DEFAULT_BUFFER_SIZE,
105           (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
106               G_PARAM_STATIC_STRINGS)));
107   g_object_class_install_property (gobject_class, PROP_OCCUPY_ALL_CHANNELS,
108       g_param_spec_boolean ("occupy-all-channels",
109           "Occupy All Channles",
110           "When enabled, ASIO device will allocate resources for all in/output "
111           "channles",
112           DEFAULT_OCCUPY_ALL_CHANNELS,
113           (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
114               G_PARAM_STATIC_STRINGS)));
115   g_object_class_install_property (gobject_class, PROP_LOOPBACK,
116       g_param_spec_boolean ("loopback", "Loopback recording",
117           "Open the sink device for loopback recording",
118           DEFAULT_LOOPBACK,
119           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
120 
121   gst_element_class_add_static_pad_template (element_class, &src_template);
122   gst_element_class_set_static_metadata (element_class, "AsioSrc",
123       "Source/Audio/Hardware",
124       "Stream audio from an audio capture device through ASIO",
125       "Seungha Yang <seungha@centricular.com>");
126 
127   basesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_asio_src_get_caps);
128 
129   audiobasesrc_class->create_ringbuffer =
130       GST_DEBUG_FUNCPTR (gst_asio_src_create_ringbuffer);
131 
132   GST_DEBUG_CATEGORY_INIT (gst_asio_src_debug, "asiosrc", 0, "asiosrc");
133 }
134 
135 static void
gst_asio_src_init(GstAsioSrc * self)136 gst_asio_src_init (GstAsioSrc * self)
137 {
138   self->buffer_size = DEFAULT_BUFFER_SIZE;
139   self->occupy_all_channels = DEFAULT_OCCUPY_ALL_CHANNELS;
140   self->loopback = DEFAULT_LOOPBACK;
141 }
142 
143 static void
gst_asio_src_finalize(GObject * object)144 gst_asio_src_finalize (GObject * object)
145 {
146   GstAsioSrc *self = GST_ASIO_SRC (object);
147 
148   g_free (self->device_clsid);
149   g_free (self->capture_channles);
150 
151   G_OBJECT_CLASS (parent_class)->finalize (object);
152 }
153 
154 static void
gst_asio_src_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)155 gst_asio_src_set_property (GObject * object, guint prop_id,
156     const GValue * value, GParamSpec * pspec)
157 {
158   GstAsioSrc *self = GST_ASIO_SRC (object);
159 
160   switch (prop_id) {
161     case PROP_DEVICE_CLSID:
162       g_free (self->device_clsid);
163       self->device_clsid = g_value_dup_string (value);
164       break;
165     case PROP_CAPTURE_CHANNELS:
166       g_free (self->capture_channles);
167       self->capture_channles = g_value_dup_string (value);
168       break;
169     case PROP_BUFFER_SIZE:
170       self->buffer_size = g_value_get_uint (value);
171       break;
172     case PROP_OCCUPY_ALL_CHANNELS:
173       self->occupy_all_channels = g_value_get_boolean (value);
174       break;
175     case PROP_LOOPBACK:
176       self->loopback = g_value_get_boolean (value);
177       break;
178     default:
179       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
180       break;
181   }
182 }
183 
184 static void
gst_asio_src_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)185 gst_asio_src_get_property (GObject * object, guint prop_id,
186     GValue * value, GParamSpec * pspec)
187 {
188   GstAsioSrc *self = GST_ASIO_SRC (object);
189 
190   switch (prop_id) {
191     case PROP_DEVICE_CLSID:
192       g_value_set_string (value, self->device_clsid);
193       break;
194     case PROP_CAPTURE_CHANNELS:
195       g_value_set_string (value, self->capture_channles);
196       break;
197     case PROP_BUFFER_SIZE:
198       g_value_set_uint (value, self->buffer_size);
199       break;
200     case PROP_OCCUPY_ALL_CHANNELS:
201       g_value_set_boolean (value, self->occupy_all_channels);
202       break;
203     case PROP_LOOPBACK:
204       g_value_set_boolean (value, self->loopback);
205       break;
206     default:
207       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
208       break;
209   }
210 }
211 
212 static GstCaps *
gst_asio_src_get_caps(GstBaseSrc * src,GstCaps * filter)213 gst_asio_src_get_caps (GstBaseSrc * src, GstCaps * filter)
214 {
215   GstAudioBaseSrc *asrc = GST_AUDIO_BASE_SRC (src);
216   GstAsioSrc *self = GST_ASIO_SRC (src);
217   GstCaps *caps = nullptr;
218 
219   if (asrc->ringbuffer)
220     caps =
221         gst_asio_ring_buffer_get_caps (GST_ASIO_RING_BUFFER (asrc->ringbuffer));
222 
223   if (!caps)
224     caps = gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (src));
225 
226   if (filter) {
227     GstCaps *filtered =
228         gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
229     gst_caps_unref (caps);
230     caps = filtered;
231   }
232 
233   GST_DEBUG_OBJECT (self, "returning caps %" GST_PTR_FORMAT, caps);
234 
235   return caps;
236 }
237 
238 static GstAudioRingBuffer *
gst_asio_src_create_ringbuffer(GstAudioBaseSrc * src)239 gst_asio_src_create_ringbuffer (GstAudioBaseSrc * src)
240 {
241   GstAsioSrc *self = GST_ASIO_SRC (src);
242   GstAsioRingBuffer *ringbuffer = nullptr;
243   HRESULT hr;
244   CLSID clsid = GUID_NULL;
245   GList *device_infos = nullptr;
246   GstAsioDeviceInfo *info = nullptr;
247   GstAsioObject *asio_object = nullptr;
248   glong max_input_ch = 0;
249   glong max_output_ch = 0;
250   guint *channel_indices = nullptr;
251   guint num_capture_channels = 0;
252   std::set < guint > channel_list;
253   guint i;
254   gchar *ringbuffer_name;
255 
256   USES_CONVERSION;
257 
258   GST_DEBUG_OBJECT (self, "Create ringbuffer");
259 
260   if (gst_asio_enum (&device_infos) == 0) {
261     GST_WARNING_OBJECT (self, "No available ASIO devices");
262     return nullptr;
263   }
264 
265   if (self->device_clsid) {
266     hr = CLSIDFromString (A2COLE (self->device_clsid), &clsid);
267     if (FAILED (hr)) {
268       GST_WARNING_OBJECT (self, "Failed to convert %s to CLSID",
269           self->device_clsid);
270       clsid = GUID_NULL;
271     }
272   }
273 
274   /* Pick the first device */
275   if (clsid == GUID_NULL) {
276     info = (GstAsioDeviceInfo *) device_infos->data;
277   } else {
278     /* Find matching device */
279     GList *iter;
280     for (iter = device_infos; iter; iter = g_list_next (iter)) {
281       GstAsioDeviceInfo *tmp = (GstAsioDeviceInfo *) iter->data;
282       if (tmp->clsid == clsid) {
283         info = tmp;
284         break;
285       }
286     }
287   }
288 
289   if (!info) {
290     GST_WARNING_OBJECT (self, "Failed to find matching device");
291     goto out;
292   }
293 
294   asio_object = gst_asio_object_new (info, self->occupy_all_channels);
295   if (!asio_object) {
296     GST_WARNING_OBJECT (self, "Failed to create ASIO object");
297     goto out;
298   }
299 
300   /* Configure channels to use */
301   if (!gst_asio_object_get_max_num_channels (asio_object, &max_input_ch,
302           &max_output_ch) || max_input_ch <= 0) {
303     GST_WARNING_OBJECT (self, "No available input channels");
304     goto out;
305   }
306 
307   /* Check if user requested specific channel(s) */
308   if (self->capture_channles) {
309     gchar **ch;
310 
311     ch = g_strsplit (self->capture_channles, ",", 0);
312 
313     num_capture_channels = g_strv_length (ch);
314     if (num_capture_channels > max_input_ch) {
315       GST_WARNING_OBJECT (self, "To many channels %d were requested",
316           num_capture_channels);
317     } else {
318       for (i = 0; i < num_capture_channels; i++) {
319         guint64 c = g_ascii_strtoull (ch[i], nullptr, 0);
320         if (c >= (guint64) max_input_ch) {
321           GST_WARNING_OBJECT (self, "Invalid channel index");
322           num_capture_channels = 0;
323           break;
324         }
325 
326         channel_list.insert ((guint) c);
327       }
328     }
329 
330     g_strfreev (ch);
331   }
332 
333   channel_indices = (guint *) g_alloca (sizeof (guint) * max_input_ch);
334   if (channel_list.size () == 0) {
335     for (i = 0; i < max_input_ch; i++)
336       channel_indices[i] = i;
337 
338     num_capture_channels = max_input_ch;
339   } else {
340     num_capture_channels = (guint) channel_list.size ();
341     i = 0;
342   for (auto iter:channel_list) {
343       channel_indices[i++] = iter;
344     }
345   }
346 
347   ringbuffer_name = g_strdup_printf ("%s-asioringbuffer",
348       GST_OBJECT_NAME (src));
349 
350   ringbuffer =
351       (GstAsioRingBuffer *) gst_asio_ring_buffer_new (asio_object,
352       self->loopback ? GST_ASIO_DEVICE_CLASS_LOOPBACK_CAPTURE :
353       GST_ASIO_DEVICE_CLASS_CAPTURE, ringbuffer_name);
354   g_free (ringbuffer_name);
355 
356   if (!ringbuffer) {
357     GST_WARNING_OBJECT (self, "Couldn't create ringbuffer object");
358     goto out;
359   }
360 
361   if (!gst_asio_ring_buffer_configure (ringbuffer, channel_indices,
362           num_capture_channels, self->buffer_size)) {
363     GST_WARNING_OBJECT (self, "Failed to configure ringbuffer");
364     gst_clear_object (&ringbuffer);
365     goto out;
366   }
367 
368 out:
369   if (device_infos)
370     g_list_free_full (device_infos, (GDestroyNotify) gst_asio_device_info_free);
371 
372   gst_clear_object (&asio_object);
373 
374   return GST_AUDIO_RING_BUFFER_CAST (ringbuffer);
375 }
376