• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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