1 /*
2 * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
3 * Copyright (C) 2018 Centricular Ltd.
4 * Author: Nirbheek Chauhan <nirbheek@centricular.com>
5 * Copyright (C) 2020 Seungha Yang <seungha@centricular.com>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22
23 /**
24 * SECTION:element-wasapi2src
25 * @title: wasapi2src
26 *
27 * Provides audio capture from the Windows Audio Session API available with
28 * Windows 10.
29 *
30 * ## Example pipelines
31 * |[
32 * gst-launch-1.0 -v wasapi2src ! fakesink
33 * ]| Capture from the default audio device and render to fakesink.
34 *
35 * |[
36 * gst-launch-1.0 -v wasapi2src low-latency=true ! fakesink
37 * ]| Capture from the default audio device with the minimum possible latency and render to fakesink.
38 *
39 */
40 #ifdef HAVE_CONFIG_H
41 #include <config.h>
42 #endif
43
44 #include "gstwasapi2src.h"
45 #include "gstwasapi2util.h"
46 #include "gstwasapi2ringbuffer.h"
47
48 GST_DEBUG_CATEGORY_STATIC (gst_wasapi2_src_debug);
49 #define GST_CAT_DEFAULT gst_wasapi2_src_debug
50
51 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
52 GST_PAD_SRC,
53 GST_PAD_ALWAYS,
54 GST_STATIC_CAPS (GST_WASAPI2_STATIC_CAPS));
55
56 #define DEFAULT_LOW_LATENCY FALSE
57 #define DEFAULT_MUTE FALSE
58 #define DEFAULT_VOLUME 1.0
59 #define DEFAULT_LOOPBACK FALSE
60
61 enum
62 {
63 PROP_0,
64 PROP_DEVICE,
65 PROP_LOW_LATENCY,
66 PROP_MUTE,
67 PROP_VOLUME,
68 PROP_DISPATCHER,
69 PROP_LOOPBACK,
70 };
71
72 struct _GstWasapi2Src
73 {
74 GstAudioBaseSrc parent;
75
76 /* properties */
77 gchar *device_id;
78 gboolean low_latency;
79 gboolean mute;
80 gdouble volume;
81 gpointer dispatcher;
82 gboolean loopback;
83
84 gboolean mute_changed;
85 gboolean volume_changed;
86 };
87
88 static void gst_wasapi2_src_finalize (GObject * object);
89 static void gst_wasapi2_src_set_property (GObject * object, guint prop_id,
90 const GValue * value, GParamSpec * pspec);
91 static void gst_wasapi2_src_get_property (GObject * object, guint prop_id,
92 GValue * value, GParamSpec * pspec);
93
94 static GstStateChangeReturn gst_wasapi2_src_change_state (GstElement *
95 element, GstStateChange transition);
96
97 static GstCaps *gst_wasapi2_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter);
98 static GstAudioRingBuffer *gst_wasapi2_src_create_ringbuffer (GstAudioBaseSrc *
99 src);
100
101 static void gst_wasapi2_src_set_mute (GstWasapi2Src * self, gboolean mute);
102 static gboolean gst_wasapi2_src_get_mute (GstWasapi2Src * self);
103 static void gst_wasapi2_src_set_volume (GstWasapi2Src * self, gdouble volume);
104 static gdouble gst_wasapi2_src_get_volume (GstWasapi2Src * self);
105
106 #define gst_wasapi2_src_parent_class parent_class
107 G_DEFINE_TYPE_WITH_CODE (GstWasapi2Src, gst_wasapi2_src,
108 GST_TYPE_AUDIO_BASE_SRC,
109 G_IMPLEMENT_INTERFACE (GST_TYPE_STREAM_VOLUME, NULL));
110
111 static void
gst_wasapi2_src_class_init(GstWasapi2SrcClass * klass)112 gst_wasapi2_src_class_init (GstWasapi2SrcClass * klass)
113 {
114 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
115 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
116 GstBaseSrcClass *basesrc_class = GST_BASE_SRC_CLASS (klass);
117 GstAudioBaseSrcClass *audiobasesrc_class = GST_AUDIO_BASE_SRC_CLASS (klass);
118
119 gobject_class->finalize = gst_wasapi2_src_finalize;
120 gobject_class->set_property = gst_wasapi2_src_set_property;
121 gobject_class->get_property = gst_wasapi2_src_get_property;
122
123 g_object_class_install_property (gobject_class, PROP_DEVICE,
124 g_param_spec_string ("device", "Device",
125 "WASAPI playback device as a GUID string",
126 NULL, GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
127 G_PARAM_STATIC_STRINGS));
128
129 g_object_class_install_property (gobject_class, PROP_LOW_LATENCY,
130 g_param_spec_boolean ("low-latency", "Low latency",
131 "Optimize all settings for lowest latency. Always safe to enable.",
132 DEFAULT_LOW_LATENCY, GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
133 G_PARAM_STATIC_STRINGS));
134
135 g_object_class_install_property (gobject_class, PROP_MUTE,
136 g_param_spec_boolean ("mute", "Mute", "Mute state of this stream",
137 DEFAULT_MUTE, GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE |
138 G_PARAM_STATIC_STRINGS));
139
140 g_object_class_install_property (gobject_class, PROP_VOLUME,
141 g_param_spec_double ("volume", "Volume", "Volume of this stream",
142 0.0, 1.0, DEFAULT_VOLUME,
143 GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE |
144 G_PARAM_STATIC_STRINGS));
145
146 /**
147 * GstWasapi2Src:dispatcher:
148 *
149 * ICoreDispatcher COM object used for activating device from UI thread.
150 *
151 * Since: 1.18
152 */
153 g_object_class_install_property (gobject_class, PROP_DISPATCHER,
154 g_param_spec_pointer ("dispatcher", "Dispatcher",
155 "ICoreDispatcher COM object to use. In order for application to ask "
156 "permission of audio device, device activation should be running "
157 "on UI thread via ICoreDispatcher. This element will increase "
158 "the reference count of given ICoreDispatcher and release it after "
159 "use. Therefore, caller does not need to consider additional "
160 "reference count management",
161 GST_PARAM_MUTABLE_READY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
162
163 /**
164 * GstWasapi2Src:loopback:
165 *
166 * Open render device for loopback recording
167 *
168 * Since: 1.20
169 */
170 g_object_class_install_property (gobject_class, PROP_LOOPBACK,
171 g_param_spec_boolean ("loopback", "Loopback recording",
172 "Open render device for loopback recording", DEFAULT_LOOPBACK,
173 GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
174 G_PARAM_STATIC_STRINGS));
175
176 gst_element_class_add_static_pad_template (element_class, &src_template);
177 gst_element_class_set_static_metadata (element_class, "Wasapi2Src",
178 "Source/Audio/Hardware",
179 "Stream audio from an audio capture device through WASAPI",
180 "Nirbheek Chauhan <nirbheek@centricular.com>, "
181 "Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>, "
182 "Seungha Yang <seungha@centricular.com>");
183
184 element_class->change_state =
185 GST_DEBUG_FUNCPTR (gst_wasapi2_src_change_state);
186
187 basesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_wasapi2_src_get_caps);
188
189 audiobasesrc_class->create_ringbuffer =
190 GST_DEBUG_FUNCPTR (gst_wasapi2_src_create_ringbuffer);
191
192 GST_DEBUG_CATEGORY_INIT (gst_wasapi2_src_debug, "wasapi2src",
193 0, "Windows audio session API source");
194 }
195
196 static void
gst_wasapi2_src_init(GstWasapi2Src * self)197 gst_wasapi2_src_init (GstWasapi2Src * self)
198 {
199 self->mute = DEFAULT_MUTE;
200 self->volume = DEFAULT_VOLUME;
201 self->low_latency = DEFAULT_LOW_LATENCY;
202 self->loopback = DEFAULT_LOOPBACK;
203 }
204
205 static void
gst_wasapi2_src_finalize(GObject * object)206 gst_wasapi2_src_finalize (GObject * object)
207 {
208 GstWasapi2Src *self = GST_WASAPI2_SRC (object);
209
210 g_free (self->device_id);
211
212 G_OBJECT_CLASS (parent_class)->finalize (object);
213 }
214
215 static void
gst_wasapi2_src_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)216 gst_wasapi2_src_set_property (GObject * object, guint prop_id,
217 const GValue * value, GParamSpec * pspec)
218 {
219 GstWasapi2Src *self = GST_WASAPI2_SRC (object);
220
221 switch (prop_id) {
222 case PROP_DEVICE:
223 g_free (self->device_id);
224 self->device_id = g_value_dup_string (value);
225 break;
226 case PROP_LOW_LATENCY:
227 self->low_latency = g_value_get_boolean (value);
228 break;
229 case PROP_MUTE:
230 gst_wasapi2_src_set_mute (self, g_value_get_boolean (value));
231 break;
232 case PROP_VOLUME:
233 gst_wasapi2_src_set_volume (self, g_value_get_double (value));
234 break;
235 case PROP_DISPATCHER:
236 self->dispatcher = g_value_get_pointer (value);
237 break;
238 case PROP_LOOPBACK:
239 self->loopback = g_value_get_boolean (value);
240 break;
241 default:
242 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
243 break;
244 }
245 }
246
247 static void
gst_wasapi2_src_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)248 gst_wasapi2_src_get_property (GObject * object, guint prop_id,
249 GValue * value, GParamSpec * pspec)
250 {
251 GstWasapi2Src *self = GST_WASAPI2_SRC (object);
252
253 switch (prop_id) {
254 case PROP_DEVICE:
255 g_value_set_string (value, self->device_id);
256 break;
257 case PROP_LOW_LATENCY:
258 g_value_set_boolean (value, self->low_latency);
259 break;
260 case PROP_MUTE:
261 g_value_set_boolean (value, gst_wasapi2_src_get_mute (self));
262 break;
263 case PROP_VOLUME:
264 g_value_set_double (value, gst_wasapi2_src_get_volume (self));
265 break;
266 case PROP_LOOPBACK:
267 g_value_set_boolean (value, self->loopback);
268 break;
269 default:
270 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
271 break;
272 }
273 }
274
275 static GstStateChangeReturn
gst_wasapi2_src_change_state(GstElement * element,GstStateChange transition)276 gst_wasapi2_src_change_state (GstElement * element, GstStateChange transition)
277 {
278 GstWasapi2Src *self = GST_WASAPI2_SRC (element);
279 GstAudioBaseSrc *asrc = GST_AUDIO_BASE_SRC_CAST (element);
280
281 switch (transition) {
282 case GST_STATE_CHANGE_READY_TO_PAUSED:
283 /* If we have pending volume/mute values to set, do here */
284 GST_OBJECT_LOCK (self);
285 if (asrc->ringbuffer) {
286 GstWasapi2RingBuffer *ringbuffer =
287 GST_WASAPI2_RING_BUFFER (asrc->ringbuffer);
288
289 if (self->volume_changed) {
290 gst_wasapi2_ring_buffer_set_volume (ringbuffer, self->volume);
291 self->volume_changed = FALSE;
292 }
293
294 if (self->mute_changed) {
295 gst_wasapi2_ring_buffer_set_mute (ringbuffer, self->mute);
296 self->mute_changed = FALSE;
297 }
298 }
299 GST_OBJECT_UNLOCK (self);
300 break;
301 default:
302 break;
303 }
304
305 return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
306 }
307
308 static GstCaps *
gst_wasapi2_src_get_caps(GstBaseSrc * bsrc,GstCaps * filter)309 gst_wasapi2_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter)
310 {
311 GstAudioBaseSrc *asrc = GST_AUDIO_BASE_SRC_CAST (bsrc);
312 GstCaps *caps = NULL;
313
314 GST_OBJECT_LOCK (bsrc);
315 if (asrc->ringbuffer) {
316 GstWasapi2RingBuffer *ringbuffer =
317 GST_WASAPI2_RING_BUFFER (asrc->ringbuffer);
318
319 gst_object_ref (ringbuffer);
320 GST_OBJECT_UNLOCK (bsrc);
321
322 /* Get caps might be able to block if device is not activated yet */
323 caps = gst_wasapi2_ring_buffer_get_caps (ringbuffer);
324 gst_object_unref (ringbuffer);
325 } else {
326 GST_OBJECT_UNLOCK (bsrc);
327 }
328
329 if (!caps)
330 caps = gst_pad_get_pad_template_caps (bsrc->srcpad);
331
332 if (filter) {
333 GstCaps *filtered =
334 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
335 gst_caps_unref (caps);
336 caps = filtered;
337 }
338
339 GST_DEBUG_OBJECT (bsrc, "returning caps %" GST_PTR_FORMAT, caps);
340
341 return caps;
342 }
343
344 static GstAudioRingBuffer *
gst_wasapi2_src_create_ringbuffer(GstAudioBaseSrc * src)345 gst_wasapi2_src_create_ringbuffer (GstAudioBaseSrc * src)
346 {
347 GstWasapi2Src *self = GST_WASAPI2_SRC (src);
348 GstAudioRingBuffer *ringbuffer;
349 gchar *name;
350 GstWasapi2ClientDeviceClass device_class =
351 GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE;
352
353 if (self->loopback)
354 device_class = GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE;
355
356 name = g_strdup_printf ("%s-ringbuffer", GST_OBJECT_NAME (src));
357
358 ringbuffer =
359 gst_wasapi2_ring_buffer_new (device_class,
360 self->low_latency, self->device_id, self->dispatcher, name);
361 g_free (name);
362
363 return ringbuffer;
364 }
365
366 static void
gst_wasapi2_src_set_mute(GstWasapi2Src * self,gboolean mute)367 gst_wasapi2_src_set_mute (GstWasapi2Src * self, gboolean mute)
368 {
369 GstAudioBaseSrc *bsrc = GST_AUDIO_BASE_SRC_CAST (self);
370 HRESULT hr;
371
372 GST_OBJECT_LOCK (self);
373
374 self->mute = mute;
375 self->mute_changed = TRUE;
376
377 if (bsrc->ringbuffer) {
378 GstWasapi2RingBuffer *ringbuffer =
379 GST_WASAPI2_RING_BUFFER (bsrc->ringbuffer);
380
381 hr = gst_wasapi2_ring_buffer_set_mute (ringbuffer, mute);
382 if (FAILED (hr)) {
383 GST_INFO_OBJECT (self, "Couldn't set mute");
384 } else {
385 self->mute_changed = FALSE;
386 }
387 }
388
389 GST_OBJECT_UNLOCK (self);
390 }
391
392 static gboolean
gst_wasapi2_src_get_mute(GstWasapi2Src * self)393 gst_wasapi2_src_get_mute (GstWasapi2Src * self)
394 {
395 GstAudioBaseSrc *bsrc = GST_AUDIO_BASE_SRC_CAST (self);
396 gboolean mute;
397 HRESULT hr;
398
399 GST_OBJECT_LOCK (self);
400
401 mute = self->mute;
402
403 if (bsrc->ringbuffer) {
404 GstWasapi2RingBuffer *ringbuffer =
405 GST_WASAPI2_RING_BUFFER (bsrc->ringbuffer);
406
407 hr = gst_wasapi2_ring_buffer_get_mute (ringbuffer, &mute);
408
409 if (FAILED (hr)) {
410 GST_INFO_OBJECT (self, "Couldn't get mute");
411 } else {
412 self->mute = mute;
413 }
414 }
415
416 GST_OBJECT_UNLOCK (self);
417
418 return mute;
419 }
420
421 static void
gst_wasapi2_src_set_volume(GstWasapi2Src * self,gdouble volume)422 gst_wasapi2_src_set_volume (GstWasapi2Src * self, gdouble volume)
423 {
424 GstAudioBaseSrc *bsrc = GST_AUDIO_BASE_SRC_CAST (self);
425 HRESULT hr;
426
427 GST_OBJECT_LOCK (self);
428
429 self->volume = volume;
430 /* clip volume value */
431 self->volume = MAX (0.0, self->volume);
432 self->volume = MIN (1.0, self->volume);
433 self->volume_changed = TRUE;
434
435 if (bsrc->ringbuffer) {
436 GstWasapi2RingBuffer *ringbuffer =
437 GST_WASAPI2_RING_BUFFER (bsrc->ringbuffer);
438
439 hr = gst_wasapi2_ring_buffer_set_volume (ringbuffer, (gfloat) self->volume);
440
441 if (FAILED (hr)) {
442 GST_INFO_OBJECT (self, "Couldn't set volume");
443 } else {
444 self->volume_changed = FALSE;
445 }
446 }
447
448 GST_OBJECT_UNLOCK (self);
449 }
450
451 static gdouble
gst_wasapi2_src_get_volume(GstWasapi2Src * self)452 gst_wasapi2_src_get_volume (GstWasapi2Src * self)
453 {
454 GstAudioBaseSrc *bsrc = GST_AUDIO_BASE_SRC_CAST (self);
455 gfloat volume;
456 HRESULT hr;
457
458 GST_OBJECT_LOCK (self);
459
460 volume = (gfloat) self->volume;
461
462 if (bsrc->ringbuffer) {
463 GstWasapi2RingBuffer *ringbuffer =
464 GST_WASAPI2_RING_BUFFER (bsrc->ringbuffer);
465
466 hr = gst_wasapi2_ring_buffer_get_volume (ringbuffer, &volume);
467
468 if (FAILED (hr)) {
469 GST_INFO_OBJECT (self, "Couldn't set volume");
470 } else {
471 self->volume = volume;
472 }
473 }
474
475 GST_OBJECT_UNLOCK (self);
476
477 volume = MAX (0.0, volume);
478 volume = MIN (1.0, volume);
479
480 return volume;
481 }
482