• 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 "gstasiosink.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_sink_debug);
32 #define GST_CAT_DEFAULT gst_asio_sink_debug
33 
34 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
35     GST_PAD_SINK,
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_OUTPUT_CHANNELS,
44   PROP_BUFFER_SIZE,
45   PROP_OCCUPY_ALL_CHANNELS,
46 };
47 
48 #define DEFAULT_BUFFER_SIZE 0
49 #define DEFAULT_OCCUPY_ALL_CHANNELS TRUE
50 
51 struct _GstAsioSink
52 {
53   GstAudioSink parent;
54 
55   /* properties */
56   gchar *device_clsid;
57   gchar *output_channels;
58   guint buffer_size;
59   gboolean occupy_all_channels;
60 };
61 
62 static void gst_asio_sink_finalize (GObject * object);
63 static void gst_asio_sink_set_property (GObject * object, guint prop_id,
64     const GValue * value, GParamSpec * pspec);
65 static void gst_asio_sink_get_property (GObject * object, guint prop_id,
66     GValue * value, GParamSpec * pspec);
67 
68 static GstCaps *gst_asio_sink_get_caps (GstBaseSink * sink, GstCaps * filter);
69 
70 static GstAudioRingBuffer *gst_asio_sink_create_ringbuffer (GstAudioBaseSink *
71     sink);
72 
73 #define gst_asio_sink_parent_class parent_class
74 G_DEFINE_TYPE (GstAsioSink, gst_asio_sink, GST_TYPE_AUDIO_BASE_SINK);
75 
76 static void
gst_asio_sink_class_init(GstAsioSinkClass * klass)77 gst_asio_sink_class_init (GstAsioSinkClass * klass)
78 {
79   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
80   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
81   GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS (klass);
82   GstAudioBaseSinkClass *audiobasesink_class =
83       GST_AUDIO_BASE_SINK_CLASS (klass);
84 
85   gobject_class->finalize = gst_asio_sink_finalize;
86   gobject_class->set_property = gst_asio_sink_set_property;
87   gobject_class->get_property = gst_asio_sink_get_property;
88 
89   g_object_class_install_property (gobject_class, PROP_DEVICE_CLSID,
90       g_param_spec_string ("device-clsid", "Device CLSID",
91           "ASIO device CLSID as a string", NULL,
92           (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
93               G_PARAM_STATIC_STRINGS)));
94   g_object_class_install_property (gobject_class, PROP_OUTPUT_CHANNELS,
95       g_param_spec_string ("output-channels", "Output Channels",
96           "Comma-separated list of ASIO channels to output", NULL,
97           (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
98               G_PARAM_STATIC_STRINGS)));
99   g_object_class_install_property (gobject_class, PROP_BUFFER_SIZE,
100       g_param_spec_uint ("buffer-size", "Buffer Size",
101           "Preferred buffer size (0 for default)",
102           0, G_MAXINT32, DEFAULT_BUFFER_SIZE,
103           (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
104               G_PARAM_STATIC_STRINGS)));
105   g_object_class_install_property (gobject_class, PROP_OCCUPY_ALL_CHANNELS,
106       g_param_spec_boolean ("occupy-all-channels",
107           "Occupy All Channles",
108           "When enabled, ASIO device will allocate resources for all in/output "
109           "channles",
110           DEFAULT_OCCUPY_ALL_CHANNELS,
111           (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
112               G_PARAM_STATIC_STRINGS)));
113 
114   gst_element_class_add_static_pad_template (element_class, &sink_template);
115   gst_element_class_set_static_metadata (element_class, "AsioSink",
116       "Source/Audio/Hardware",
117       "Stream audio from an audio capture device through ASIO",
118       "Seungha Yang <seungha@centricular.com>");
119 
120   basesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_asio_sink_get_caps);
121 
122   audiobasesink_class->create_ringbuffer =
123       GST_DEBUG_FUNCPTR (gst_asio_sink_create_ringbuffer);
124 
125   GST_DEBUG_CATEGORY_INIT (gst_asio_sink_debug, "asiosink", 0, "asiosink");
126 }
127 
128 static void
gst_asio_sink_init(GstAsioSink * self)129 gst_asio_sink_init (GstAsioSink * self)
130 {
131   self->buffer_size = DEFAULT_BUFFER_SIZE;
132   self->occupy_all_channels = DEFAULT_OCCUPY_ALL_CHANNELS;
133 }
134 
135 static void
gst_asio_sink_finalize(GObject * object)136 gst_asio_sink_finalize (GObject * object)
137 {
138   GstAsioSink *self = GST_ASIO_SINK (object);
139 
140   g_free (self->device_clsid);
141   g_free (self->output_channels);
142 
143   G_OBJECT_CLASS (parent_class)->finalize (object);
144 }
145 
146 static void
gst_asio_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)147 gst_asio_sink_set_property (GObject * object, guint prop_id,
148     const GValue * value, GParamSpec * pspec)
149 {
150   GstAsioSink *self = GST_ASIO_SINK (object);
151 
152   switch (prop_id) {
153     case PROP_DEVICE_CLSID:
154       g_free (self->device_clsid);
155       self->device_clsid = g_value_dup_string (value);
156       break;
157     case PROP_OUTPUT_CHANNELS:
158       g_free (self->output_channels);
159       self->output_channels = g_value_dup_string (value);
160       break;
161     case PROP_BUFFER_SIZE:
162       self->buffer_size = g_value_get_uint (value);
163       break;
164     case PROP_OCCUPY_ALL_CHANNELS:
165       self->occupy_all_channels = g_value_get_boolean (value);
166       break;
167     default:
168       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
169       break;
170   }
171 }
172 
173 static void
gst_asio_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)174 gst_asio_sink_get_property (GObject * object, guint prop_id,
175     GValue * value, GParamSpec * pspec)
176 {
177   GstAsioSink *self = GST_ASIO_SINK (object);
178 
179   switch (prop_id) {
180     case PROP_DEVICE_CLSID:
181       g_value_set_string (value, self->device_clsid);
182       break;
183     case PROP_OUTPUT_CHANNELS:
184       g_value_set_string (value, self->output_channels);
185       break;
186     case PROP_BUFFER_SIZE:
187       g_value_set_uint (value, self->buffer_size);
188       break;
189     case PROP_OCCUPY_ALL_CHANNELS:
190       g_value_set_boolean (value, self->occupy_all_channels);
191       break;
192     default:
193       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
194       break;
195   }
196 }
197 
198 static GstCaps *
gst_asio_sink_get_caps(GstBaseSink * sink,GstCaps * filter)199 gst_asio_sink_get_caps (GstBaseSink * sink, GstCaps * filter)
200 {
201   GstAudioBaseSink *asink = GST_AUDIO_BASE_SINK (sink);
202   GstAsioSink *self = GST_ASIO_SINK (sink);
203   GstCaps *caps = nullptr;
204 
205   if (asink->ringbuffer)
206     caps =
207         gst_asio_ring_buffer_get_caps (GST_ASIO_RING_BUFFER
208         (asink->ringbuffer));
209 
210   if (!caps)
211     caps = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (sink));
212 
213   if (filter) {
214     GstCaps *filtered =
215         gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
216     gst_caps_unref (caps);
217     caps = filtered;
218   }
219 
220   GST_DEBUG_OBJECT (self, "returning caps %" GST_PTR_FORMAT, caps);
221 
222   return caps;
223 }
224 
225 static GstAudioRingBuffer *
gst_asio_sink_create_ringbuffer(GstAudioBaseSink * sink)226 gst_asio_sink_create_ringbuffer (GstAudioBaseSink * sink)
227 {
228   GstAsioSink *self = GST_ASIO_SINK (sink);
229   GstAsioRingBuffer *ringbuffer = nullptr;
230   HRESULT hr;
231   CLSID clsid = GUID_NULL;
232   GList *device_infos = nullptr;
233   GstAsioDeviceInfo *info = nullptr;
234   GstAsioObject *asio_object = nullptr;
235   glong max_input_ch = 0;
236   glong max_output_ch = 0;
237   guint *channel_indices = nullptr;
238   guint num_capture_channels = 0;
239   std::set < guint > channel_list;
240   guint i;
241   gchar *ringbuffer_name;
242 
243   USES_CONVERSION;
244 
245   GST_DEBUG_OBJECT (self, "Create ringbuffer");
246 
247   if (gst_asio_enum (&device_infos) == 0) {
248     GST_WARNING_OBJECT (self, "No available ASIO devices");
249     return nullptr;
250   }
251 
252   if (self->device_clsid) {
253     hr = CLSIDFromString (A2COLE (self->device_clsid), &clsid);
254     if (FAILED (hr)) {
255       GST_WARNING_OBJECT (self, "Failed to convert %s to CLSID",
256           self->device_clsid);
257       clsid = GUID_NULL;
258     }
259   }
260 
261   /* Pick the first device */
262   if (clsid == GUID_NULL) {
263     info = (GstAsioDeviceInfo *) device_infos->data;
264   } else {
265     /* Find matching device */
266     GList *iter;
267     for (iter = device_infos; iter; iter = g_list_next (iter)) {
268       GstAsioDeviceInfo *tmp = (GstAsioDeviceInfo *) iter->data;
269       if (tmp->clsid == clsid) {
270         info = tmp;
271         break;
272       }
273     }
274   }
275 
276   if (!info) {
277     GST_WARNING_OBJECT (self, "Failed to find matching device");
278     goto out;
279   }
280 
281   asio_object = gst_asio_object_new (info, self->occupy_all_channels);
282   if (!asio_object) {
283     GST_WARNING_OBJECT (self, "Failed to create ASIO object");
284     goto out;
285   }
286 
287   /* Configure channels to use */
288   if (!gst_asio_object_get_max_num_channels (asio_object, &max_input_ch,
289           &max_output_ch) || max_input_ch <= 0) {
290     GST_WARNING_OBJECT (self, "No available input channels");
291     goto out;
292   }
293 
294   /* Check if user requested specific channle(s) */
295   if (self->output_channels) {
296     gchar **ch;
297 
298     ch = g_strsplit (self->output_channels, ",", 0);
299 
300     num_capture_channels = g_strv_length (ch);
301     if (num_capture_channels > max_input_ch) {
302       GST_WARNING_OBJECT (self, "To many channels %d were requested",
303           num_capture_channels);
304     } else {
305       for (i = 0; i < num_capture_channels; i++) {
306         guint64 c = g_ascii_strtoull (ch[i], nullptr, 0);
307         if (c >= (guint64) max_input_ch) {
308           GST_WARNING_OBJECT (self, "Invalid channel index");
309           num_capture_channels = 0;
310           break;
311         }
312 
313         channel_list.insert ((guint) c);
314       }
315     }
316 
317     g_strfreev (ch);
318   }
319 
320   channel_indices = (guint *) g_alloca (sizeof (guint) * max_input_ch);
321   if (channel_list.size () == 0) {
322     for (i = 0; i < max_input_ch; i++)
323       channel_indices[i] = i;
324 
325     num_capture_channels = max_input_ch;
326   } else {
327     num_capture_channels = (guint) channel_list.size ();
328     i = 0;
329   for (auto iter:channel_list) {
330       channel_indices[i++] = iter;
331     }
332   }
333 
334   ringbuffer_name = g_strdup_printf ("%s-asioringbuffer",
335       GST_OBJECT_NAME (sink));
336 
337   ringbuffer =
338       (GstAsioRingBuffer *) gst_asio_ring_buffer_new (asio_object,
339       GST_ASIO_DEVICE_CLASS_RENDER, ringbuffer_name);
340   g_free (ringbuffer_name);
341 
342   if (!ringbuffer) {
343     GST_WARNING_OBJECT (self, "Couldn't create ringbuffer object");
344     goto out;
345   }
346 
347   if (!gst_asio_ring_buffer_configure (ringbuffer, channel_indices,
348           num_capture_channels, self->buffer_size)) {
349     GST_WARNING_OBJECT (self, "Failed to configure ringbuffer");
350     gst_clear_object (&ringbuffer);
351     goto out;
352   }
353 
354 out:
355   if (device_infos)
356     g_list_free_full (device_infos, (GDestroyNotify) gst_asio_device_info_free);
357 
358   gst_clear_object (&asio_object);
359 
360   return GST_AUDIO_RING_BUFFER_CAST (ringbuffer);
361 }
362