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