1 /*
2  * GStreamer
3  * Copyright 2005 Thomas Vander Stichele <thomas@apestaart.org>
4  * Copyright 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
5  * Copyright 2005 S�bastien Moutte <sebastien@moutte.net>
6  * Copyright 2006 Joni Valtanen <joni.valtanen@movial.fi>
7  *
8  * Permission is hereby granted, free of charge, to any person obtaining a
9  * copy of this software and associated documentation files (the "Software"),
10  * to deal in the Software without restriction, including without limitation
11  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12  * and/or sell copies of the Software, and to permit persons to whom the
13  * Software is furnished to do so, subject to the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be included in
16  * all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24  * DEALINGS IN THE SOFTWARE.
25  *
26  * Alternatively, the contents of this file may be used under the
27  * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
28  * which case the following provisions apply instead of the ones
29  * mentioned above:
30  *
31  * This library is free software; you can redistribute it and/or
32  * modify it under the terms of the GNU Library General Public
33  * License as published by the Free Software Foundation; either
34  * version 2 of the License, or (at your option) any later version.
35  *
36  * This library is distributed in the hope that it will be useful,
37  * but WITHOUT ANY WARRANTY; without even the implied warranty of
38  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
39  * Library General Public License for more details.
40  *
41  * You should have received a copy of the GNU Library General Public
42  * License along with this library; if not, write to the
43  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
44  * Boston, MA 02110-1301, USA.
45  */
46 
47 /*
48   TODO: add mixer device init for selection by device-guid
49 */
50 
51 /**
52  * SECTION:element-directsoundsrc
53  * @title: directsoundsrc
54  *
55  * Reads audio data using the DirectSound API.
56  *
57  * ## Example pipelines
58  * |[
59  * gst-launch-1.0 -v directsoundsrc ! audioconvert ! vorbisenc ! oggmux ! filesink location=dsound.ogg
60  * ]| Record from DirectSound and encode to Ogg/Vorbis.
61  *
62  */
63 
64 #ifdef HAVE_CONFIG_H
65 #  include "config.h"
66 #endif
67 
68 #include <gst/gst.h>
69 #include <gst/audio/audio.h>
70 #include <gst/audio/gstaudiobasesrc.h>
71 
72 #include "gstdirectsoundsrc.h"
73 
74 #include <windows.h>
75 #include <dsound.h>
76 #include <mmsystem.h>
77 #include <stdio.h>
78 
79 GST_DEBUG_CATEGORY_STATIC (directsoundsrc_debug);
80 #define GST_CAT_DEFAULT directsoundsrc_debug
81 
82 /* defaults here */
83 #define DEFAULT_DEVICE 0
84 #define DEFAULT_MUTE FALSE
85 
86 /* properties */
87 enum
88 {
89   PROP_0,
90   PROP_DEVICE_NAME,
91   PROP_DEVICE,
92   PROP_VOLUME,
93   PROP_MUTE
94 };
95 
96 static void gst_directsound_src_finalize (GObject * object);
97 
98 static void gst_directsound_src_set_property (GObject * object,
99     guint prop_id, const GValue * value, GParamSpec * pspec);
100 
101 static void gst_directsound_src_get_property (GObject * object,
102     guint prop_id, GValue * value, GParamSpec * pspec);
103 
104 static gboolean gst_directsound_src_open (GstAudioSrc * asrc);
105 static gboolean gst_directsound_src_close (GstAudioSrc * asrc);
106 static gboolean gst_directsound_src_prepare (GstAudioSrc * asrc,
107     GstAudioRingBufferSpec * spec);
108 static gboolean gst_directsound_src_unprepare (GstAudioSrc * asrc);
109 static void gst_directsound_src_reset (GstAudioSrc * asrc);
110 static GstCaps *gst_directsound_src_getcaps (GstBaseSrc * bsrc,
111     GstCaps * filter);
112 
113 static guint gst_directsound_src_read (GstAudioSrc * asrc,
114     gpointer data, guint length, GstClockTime * timestamp);
115 
116 static void gst_directsound_src_dispose (GObject * object);
117 
118 static guint gst_directsound_src_delay (GstAudioSrc * asrc);
119 
120 static gboolean gst_directsound_src_mixer_find (GstDirectSoundSrc * dsoundsrc,
121     MIXERCAPS * mixer_caps);
122 static void gst_directsound_src_mixer_init (GstDirectSoundSrc * dsoundsrc);
123 
124 static gdouble gst_directsound_src_get_volume (GstDirectSoundSrc * dsoundsrc);
125 static void gst_directsound_src_set_volume (GstDirectSoundSrc * dsoundsrc,
126     gdouble volume);
127 
128 static gboolean gst_directsound_src_get_mute (GstDirectSoundSrc * dsoundsrc);
129 static void gst_directsound_src_set_mute (GstDirectSoundSrc * dsoundsrc,
130     gboolean mute);
131 
132 static const gchar *gst_directsound_src_get_device (GstDirectSoundSrc *
133     dsoundsrc);
134 static void gst_directsound_src_set_device (GstDirectSoundSrc * dsoundsrc,
135     const gchar * device_id);
136 
137 static GstStaticPadTemplate directsound_src_src_factory =
138 GST_STATIC_PAD_TEMPLATE ("src",
139     GST_PAD_SRC,
140     GST_PAD_ALWAYS,
141     GST_STATIC_CAPS (GST_DIRECTSOUND_SRC_CAPS));
142 
143 #define gst_directsound_src_parent_class parent_class
144 G_DEFINE_TYPE_WITH_CODE (GstDirectSoundSrc, gst_directsound_src,
145     GST_TYPE_AUDIO_SRC, G_IMPLEMENT_INTERFACE (GST_TYPE_STREAM_VOLUME, NULL)
146     );
147 
148 static void
gst_directsound_src_dispose(GObject * object)149 gst_directsound_src_dispose (GObject * object)
150 {
151   G_OBJECT_CLASS (parent_class)->dispose (object);
152 }
153 
154 static void
gst_directsound_src_finalize(GObject * object)155 gst_directsound_src_finalize (GObject * object)
156 {
157   GstDirectSoundSrc *dsoundsrc = GST_DIRECTSOUND_SRC (object);
158 
159   g_mutex_clear (&dsoundsrc->dsound_lock);
160   gst_object_unref (dsoundsrc->system_clock);
161   if (dsoundsrc->read_wait_clock_id != NULL)
162     gst_clock_id_unref (dsoundsrc->read_wait_clock_id);
163 
164   g_free (dsoundsrc->device_name);
165 
166   g_free (dsoundsrc->device_id);
167 
168   g_free (dsoundsrc->device_guid);
169 
170   G_OBJECT_CLASS (parent_class)->finalize (object);
171 }
172 
173 static void
gst_directsound_src_class_init(GstDirectSoundSrcClass * klass)174 gst_directsound_src_class_init (GstDirectSoundSrcClass * klass)
175 {
176   GObjectClass *gobject_class;
177   GstElementClass *gstelement_class;
178   GstBaseSrcClass *gstbasesrc_class;
179   GstAudioSrcClass *gstaudiosrc_class;
180 
181   gobject_class = (GObjectClass *) klass;
182   gstelement_class = (GstElementClass *) klass;
183   gstbasesrc_class = (GstBaseSrcClass *) klass;
184   gstaudiosrc_class = (GstAudioSrcClass *) klass;
185 
186   GST_DEBUG_CATEGORY_INIT (directsoundsrc_debug, "directsoundsrc", 0,
187       "DirectSound Src");
188 
189   GST_DEBUG ("initializing directsoundsrc class");
190 
191   gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_directsound_src_finalize);
192   gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_directsound_src_dispose);
193   gobject_class->get_property =
194       GST_DEBUG_FUNCPTR (gst_directsound_src_get_property);
195   gobject_class->set_property =
196       GST_DEBUG_FUNCPTR (gst_directsound_src_set_property);
197 
198   gstbasesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_directsound_src_getcaps);
199 
200   gstaudiosrc_class->open = GST_DEBUG_FUNCPTR (gst_directsound_src_open);
201   gstaudiosrc_class->close = GST_DEBUG_FUNCPTR (gst_directsound_src_close);
202   gstaudiosrc_class->read = GST_DEBUG_FUNCPTR (gst_directsound_src_read);
203   gstaudiosrc_class->prepare = GST_DEBUG_FUNCPTR (gst_directsound_src_prepare);
204   gstaudiosrc_class->unprepare =
205       GST_DEBUG_FUNCPTR (gst_directsound_src_unprepare);
206   gstaudiosrc_class->delay = GST_DEBUG_FUNCPTR (gst_directsound_src_delay);
207   gstaudiosrc_class->reset = GST_DEBUG_FUNCPTR (gst_directsound_src_reset);
208 
209   gst_element_class_set_static_metadata (gstelement_class,
210       "DirectSound audio source", "Source/Audio",
211       "Capture from a soundcard via DirectSound",
212       "Joni Valtanen <joni.valtanen@movial.fi>");
213 
214   gst_element_class_add_static_pad_template (gstelement_class,
215       &directsound_src_src_factory);
216 
217   g_object_class_install_property
218       (gobject_class, PROP_DEVICE_NAME,
219       g_param_spec_string ("device-name", "Device name",
220           "Human-readable name of the sound device", NULL, G_PARAM_READWRITE));
221 
222   g_object_class_install_property (gobject_class,
223       PROP_DEVICE,
224       g_param_spec_string ("device", "Device",
225           "DirectSound playback device as a GUID string (volume and mute will not work!)",
226           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
227 
228   g_object_class_install_property
229       (gobject_class, PROP_VOLUME,
230       g_param_spec_double ("volume", "Volume",
231           "Volume of this stream", 0.0, 1.0, 1.0,
232           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
233 
234   g_object_class_install_property
235       (gobject_class, PROP_MUTE,
236       g_param_spec_boolean ("mute", "Mute",
237           "Mute state of this stream", DEFAULT_MUTE,
238           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
239 }
240 
241 static GstCaps *
gst_directsound_src_getcaps(GstBaseSrc * bsrc,GstCaps * filter)242 gst_directsound_src_getcaps (GstBaseSrc * bsrc, GstCaps * filter)
243 {
244   GstCaps *caps = NULL;
245   GST_DEBUG_OBJECT (bsrc, "get caps");
246 
247   caps = gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (bsrc));
248   return caps;
249 }
250 
251 static void
gst_directsound_src_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)252 gst_directsound_src_set_property (GObject * object, guint prop_id,
253     const GValue * value, GParamSpec * pspec)
254 {
255   GstDirectSoundSrc *src = GST_DIRECTSOUND_SRC (object);
256   GST_DEBUG ("set property");
257 
258   switch (prop_id) {
259     case PROP_DEVICE_NAME:
260       if (src->device_name) {
261         g_free (src->device_name);
262         src->device_name = NULL;
263       }
264       if (g_value_get_string (value)) {
265         src->device_name = g_strdup (g_value_get_string (value));
266       }
267       break;
268     case PROP_VOLUME:
269       gst_directsound_src_set_volume (src, g_value_get_double (value));
270       break;
271     case PROP_MUTE:
272       gst_directsound_src_set_mute (src, g_value_get_boolean (value));
273       break;
274     case PROP_DEVICE:
275       gst_directsound_src_set_device (src, g_value_get_string (value));
276       break;
277     default:
278       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
279       break;
280   }
281 }
282 
283 static void
gst_directsound_src_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)284 gst_directsound_src_get_property (GObject * object, guint prop_id,
285     GValue * value, GParamSpec * pspec)
286 {
287   GstDirectSoundSrc *src = GST_DIRECTSOUND_SRC (object);
288 
289   GST_DEBUG ("get property");
290 
291   switch (prop_id) {
292     case PROP_DEVICE_NAME:
293       g_value_set_string (value, src->device_name);
294       break;
295     case PROP_DEVICE:
296       g_value_set_string (value, gst_directsound_src_get_device (src));
297       break;
298     case PROP_VOLUME:
299       g_value_set_double (value, gst_directsound_src_get_volume (src));
300       break;
301     case PROP_MUTE:
302       g_value_set_boolean (value, gst_directsound_src_get_mute (src));
303       break;
304     default:
305       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
306       break;
307   }
308 }
309 
310 
311 /* initialize the new element
312  * instantiate pads and add them to element
313  * set functions
314  * initialize structure
315  */
316 static void
gst_directsound_src_init(GstDirectSoundSrc * src)317 gst_directsound_src_init (GstDirectSoundSrc * src)
318 {
319   GST_DEBUG_OBJECT (src, "initializing directsoundsrc");
320   g_mutex_init (&src->dsound_lock);
321   src->system_clock = gst_system_clock_obtain ();
322   src->read_wait_clock_id = NULL;
323   src->reset_while_sleeping = FALSE;
324   src->device_guid = NULL;
325   src->device_id = NULL;
326   src->device_name = NULL;
327   src->mixer = NULL;
328   src->control_id_mute = -1;
329   src->control_id_volume = -1;
330   src->volume = 100;
331   src->mute = FALSE;
332 }
333 
334 
335 /* Enumeration callback called by DirectSoundCaptureEnumerate.
336  * Gets the GUID of request audio device
337  */
338 static BOOL CALLBACK
gst_directsound_enum_callback(GUID * pGUID,TCHAR * strDesc,TCHAR * strDrvName,VOID * pContext)339 gst_directsound_enum_callback (GUID * pGUID, TCHAR * strDesc,
340     TCHAR * strDrvName, VOID * pContext)
341 {
342   GstDirectSoundSrc *dsoundsrc = GST_DIRECTSOUND_SRC (pContext);
343   gchar *driver, *description;
344 
345   description = g_locale_to_utf8 (strDesc, -1, NULL, NULL, NULL);
346   if (!description) {
347     GST_ERROR_OBJECT (dsoundsrc,
348         "Failed to convert description from locale encoding to UTF8");
349     return TRUE;
350   }
351 
352   driver = g_locale_to_utf8 (strDrvName, -1, NULL, NULL, NULL);
353 
354   if (pGUID && dsoundsrc && dsoundsrc->device_name &&
355       !g_strcmp0 (dsoundsrc->device_name, description)) {
356     g_free (dsoundsrc->device_guid);
357     dsoundsrc->device_guid = (GUID *) g_malloc0 (sizeof (GUID));
358     memcpy (dsoundsrc->device_guid, pGUID, sizeof (GUID));
359     GST_INFO_OBJECT (dsoundsrc, "found the requested audio device :%s",
360         dsoundsrc->device_name);
361     g_free (description);
362     g_free (driver);
363     return FALSE;
364   }
365 
366   GST_INFO_OBJECT (dsoundsrc, "sound device names: %s, %s, requested device:%s",
367       description, driver, dsoundsrc->device_name);
368 
369   g_free (description);
370   g_free (driver);
371 
372   return TRUE;
373 }
374 
375 static LPGUID
string_to_guid(const gchar * str)376 string_to_guid (const gchar * str)
377 {
378   HRESULT ret;
379   gunichar2 *wstr;
380   LPGUID out;
381 
382   wstr = g_utf8_to_utf16 (str, -1, NULL, NULL, NULL);
383   if (!wstr)
384     return NULL;
385 
386   out = g_new (GUID, 1);
387   ret = CLSIDFromString ((LPOLESTR) wstr, out);
388   g_free (wstr);
389   if (ret != NOERROR) {
390     g_free (out);
391     return NULL;
392   }
393 
394   return out;
395 }
396 
397 static gboolean
gst_directsound_src_open(GstAudioSrc * asrc)398 gst_directsound_src_open (GstAudioSrc * asrc)
399 {
400   GstDirectSoundSrc *dsoundsrc;
401   HRESULT hRes;                 /* Result for windows functions */
402 
403   GST_DEBUG_OBJECT (asrc, "opening directsoundsrc");
404 
405   dsoundsrc = GST_DIRECTSOUND_SRC (asrc);
406 
407   if (dsoundsrc->device_id) {
408     GST_DEBUG_OBJECT (asrc, "device id set to: %s ", dsoundsrc->device_id);
409     dsoundsrc->device_guid = string_to_guid (dsoundsrc->device_id);
410     if (dsoundsrc->device_guid == NULL) {
411       GST_ELEMENT_ERROR (dsoundsrc, RESOURCE, OPEN_READ,
412           ("gst_directsound_src_open: device set, but guid not found: %s",
413               dsoundsrc->device_id), (NULL));
414       g_free (dsoundsrc->device_guid);
415       return FALSE;
416     }
417   } else {
418 
419     hRes = DirectSoundCaptureEnumerate ((LPDSENUMCALLBACK)
420         gst_directsound_enum_callback, (VOID *) dsoundsrc);
421 
422     if (FAILED (hRes)) {
423       goto capture_enumerate;
424     }
425   }
426   /* Create capture object */
427   hRes =
428       DirectSoundCaptureCreate (dsoundsrc->device_guid, &dsoundsrc->pDSC, NULL);
429 
430 
431   if (FAILED (hRes)) {
432     goto capture_object;
433   }
434   // mixer is only supported when device-id is not set
435   if (!dsoundsrc->device_id) {
436     gst_directsound_src_mixer_init (dsoundsrc);
437   }
438 
439   return TRUE;
440 
441 capture_enumerate:
442   {
443     GST_ELEMENT_ERROR (dsoundsrc, RESOURCE, OPEN_READ,
444         ("Unable to enumerate audio capture devices"), (NULL));
445     return FALSE;
446   }
447 capture_object:
448   {
449     GST_ELEMENT_ERROR (dsoundsrc, RESOURCE, OPEN_READ,
450         ("Unable to create capture object"), (NULL));
451     return FALSE;
452   }
453 }
454 
455 static gboolean
gst_directsound_src_close(GstAudioSrc * asrc)456 gst_directsound_src_close (GstAudioSrc * asrc)
457 {
458   GstDirectSoundSrc *dsoundsrc;
459 
460   GST_DEBUG_OBJECT (asrc, "closing directsoundsrc");
461 
462   dsoundsrc = GST_DIRECTSOUND_SRC (asrc);
463 
464   /* Release capture handler  */
465   IDirectSoundCapture_Release (dsoundsrc->pDSC);
466 
467   if (dsoundsrc->mixer)
468     mixerClose (dsoundsrc->mixer);
469 
470   return TRUE;
471 }
472 
473 static gboolean
gst_directsound_src_prepare(GstAudioSrc * asrc,GstAudioRingBufferSpec * spec)474 gst_directsound_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec)
475 {
476   GstDirectSoundSrc *dsoundsrc;
477   WAVEFORMATEX wfx;             /* Wave format structure */
478   HRESULT hRes;                 /* Result for windows functions */
479   DSCBUFFERDESC descSecondary;  /* Capturebuffer description */
480 
481   dsoundsrc = GST_DIRECTSOUND_SRC (asrc);
482 
483   GST_DEBUG_OBJECT (asrc, "preparing directsoundsrc");
484 
485   /* Define buffer */
486   memset (&wfx, 0, sizeof (WAVEFORMATEX));
487   wfx.wFormatTag = WAVE_FORMAT_PCM;
488   wfx.nChannels = GST_AUDIO_INFO_CHANNELS (&spec->info);
489   wfx.nSamplesPerSec = GST_AUDIO_INFO_RATE (&spec->info);
490   wfx.wBitsPerSample = GST_AUDIO_INFO_BPF (&spec->info) * 8 / wfx.nChannels;
491   wfx.nBlockAlign = GST_AUDIO_INFO_BPF (&spec->info);
492   wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
493   /* Ignored for WAVE_FORMAT_PCM. */
494   wfx.cbSize = 0;
495 
496   if (wfx.wBitsPerSample != 16 && wfx.wBitsPerSample != 8)
497     goto dodgy_width;
498 
499   GST_INFO_OBJECT (asrc, "latency time: %" G_GUINT64_FORMAT " - buffer time: %"
500       G_GUINT64_FORMAT, spec->latency_time, spec->buffer_time);
501 
502   /* Buffer-time should always be >= 2*latency */
503   if (spec->buffer_time < spec->latency_time * 2) {
504     spec->buffer_time = spec->latency_time * 2;
505     GST_WARNING ("buffer-time was less than 2*latency-time, clamping");
506   }
507 
508   /* Set the buffer size from our configured buffer time (in microsecs) */
509   dsoundsrc->buffer_size =
510       gst_util_uint64_scale_int (spec->buffer_time, wfx.nAvgBytesPerSec,
511       GST_SECOND / GST_USECOND);
512 
513   GST_INFO_OBJECT (asrc, "Buffer size: %d", dsoundsrc->buffer_size);
514 
515   spec->segsize =
516       gst_util_uint64_scale (spec->latency_time, wfx.nAvgBytesPerSec,
517       GST_SECOND / GST_USECOND);
518 
519   /* Sanitized segsize */
520   if (spec->segsize < GST_AUDIO_INFO_BPF (&spec->info))
521     spec->segsize = GST_AUDIO_INFO_BPF (&spec->info);
522   else if (spec->segsize % GST_AUDIO_INFO_BPF (&spec->info) != 0)
523     spec->segsize =
524         ((spec->segsize + GST_AUDIO_INFO_BPF (&spec->info) -
525             1) / GST_AUDIO_INFO_BPF (&spec->info)) *
526         GST_AUDIO_INFO_BPF (&spec->info);
527   spec->segtotal = dsoundsrc->buffer_size / spec->segsize;
528   /* The device usually takes time = 1-2 segments to start producing buffers */
529   spec->seglatency = spec->segtotal + 2;
530 
531   /* Fetch and set the actual latency time that will be used */
532   dsoundsrc->latency_time =
533       gst_util_uint64_scale (spec->segsize, GST_SECOND / GST_USECOND,
534       GST_AUDIO_INFO_BPF (&spec->info) * GST_AUDIO_INFO_RATE (&spec->info));
535 
536   GST_INFO_OBJECT (asrc, "actual latency time: %" G_GUINT64_FORMAT,
537       spec->latency_time);
538 
539   /* Init secondary buffer desciption */
540   memset (&descSecondary, 0, sizeof (DSCBUFFERDESC));
541   descSecondary.dwSize = sizeof (DSCBUFFERDESC);
542   descSecondary.dwFlags = 0;
543   descSecondary.dwReserved = 0;
544 
545   /* This is not primary buffer so have to set size  */
546   descSecondary.dwBufferBytes = dsoundsrc->buffer_size;
547   descSecondary.lpwfxFormat = &wfx;
548 
549   /* Create buffer */
550   hRes = IDirectSoundCapture_CreateCaptureBuffer (dsoundsrc->pDSC,
551       &descSecondary, &dsoundsrc->pDSBSecondary, NULL);
552   if (hRes != DS_OK)
553     goto capture_buffer;
554 
555   dsoundsrc->bytes_per_sample = GST_AUDIO_INFO_BPF (&spec->info);
556 
557   GST_INFO_OBJECT (asrc,
558       "bytes/sec: %lu, buffer size: %d, segsize: %d, segtotal: %d",
559       wfx.nAvgBytesPerSec, dsoundsrc->buffer_size, spec->segsize,
560       spec->segtotal);
561 
562   /* Not read anything yet */
563   dsoundsrc->current_circular_offset = 0;
564 
565   GST_INFO_OBJECT (asrc, "channels: %d, rate: %d, bytes_per_sample: %d"
566       " WAVEFORMATEX.nSamplesPerSec: %ld, WAVEFORMATEX.wBitsPerSample: %d,"
567       " WAVEFORMATEX.nBlockAlign: %d, WAVEFORMATEX.nAvgBytesPerSec: %ld",
568       GST_AUDIO_INFO_CHANNELS (&spec->info), GST_AUDIO_INFO_RATE (&spec->info),
569       GST_AUDIO_INFO_BPF (&spec->info), wfx.nSamplesPerSec, wfx.wBitsPerSample,
570       wfx.nBlockAlign, wfx.nAvgBytesPerSec);
571 
572   return TRUE;
573 
574 capture_buffer:
575   {
576     GST_ELEMENT_ERROR (dsoundsrc, RESOURCE, OPEN_READ,
577         ("Unable to create capturebuffer"), (NULL));
578     return FALSE;
579   }
580 dodgy_width:
581   {
582     GST_ELEMENT_ERROR (dsoundsrc, RESOURCE, OPEN_READ,
583         ("Unexpected width %d", wfx.wBitsPerSample), (NULL));
584     return FALSE;
585   }
586 }
587 
588 static gboolean
gst_directsound_src_unprepare(GstAudioSrc * asrc)589 gst_directsound_src_unprepare (GstAudioSrc * asrc)
590 {
591   GstDirectSoundSrc *dsoundsrc;
592 
593   GST_DEBUG_OBJECT (asrc, "unpreparing directsoundsrc");
594 
595   dsoundsrc = GST_DIRECTSOUND_SRC (asrc);
596 
597   GST_DSOUND_LOCK (dsoundsrc);
598 
599   /* Stop capturing */
600   IDirectSoundCaptureBuffer_Stop (dsoundsrc->pDSBSecondary);
601 
602   /* Release buffer  */
603   IDirectSoundCaptureBuffer_Release (dsoundsrc->pDSBSecondary);
604   GST_DSOUND_UNLOCK (dsoundsrc);
605   return TRUE;
606 }
607 
608 /*
609 return number of readed bytes */
610 static guint
gst_directsound_src_read(GstAudioSrc * asrc,gpointer data,guint length,GstClockTime * timestamp)611 gst_directsound_src_read (GstAudioSrc * asrc, gpointer data, guint length,
612     GstClockTime * timestamp)
613 {
614   GstDirectSoundSrc *dsoundsrc;
615   guint64 sleep_time_ms, sleep_until;
616   GstClockID clock_id;
617 
618   HRESULT hRes;                 /* Result for windows functions */
619   DWORD dwCurrentCaptureCursor = 0;
620   DWORD dwBufferSize = 0;
621 
622   LPVOID pLockedBuffer1 = NULL;
623   LPVOID pLockedBuffer2 = NULL;
624   DWORD dwSizeBuffer1 = 0;
625   DWORD dwSizeBuffer2 = 0;
626 
627   DWORD dwStatus = 0;
628 
629   GST_DEBUG_OBJECT (asrc, "reading directsoundsrc");
630 
631   dsoundsrc = GST_DIRECTSOUND_SRC (asrc);
632 
633   GST_DSOUND_LOCK (dsoundsrc);
634 
635   /* Get current buffer status */
636   hRes = IDirectSoundCaptureBuffer_GetStatus (dsoundsrc->pDSBSecondary,
637       &dwStatus);
638 
639   if (FAILED (hRes)) {
640     GST_DSOUND_UNLOCK (dsoundsrc);
641     return -1;
642   }
643 
644   /* Starting capturing if not already */
645   if (!(dwStatus & DSCBSTATUS_CAPTURING)) {
646     hRes = IDirectSoundCaptureBuffer_Start (dsoundsrc->pDSBSecondary,
647         DSCBSTART_LOOPING);
648     GST_INFO_OBJECT (asrc, "capture started");
649   }
650 
651   /* Loop till the source has produced bytes equal to or greater than @length.
652    *
653    * DirectSound has a notification-based API that uses Windows CreateEvent()
654    * + WaitForSingleObject(), but it is completely useless for live streams.
655    *
656    *  1. You must schedule all events before starting capture
657    *  2. The events are all fired exactly once
658    *  3. You cannot schedule new events while a capture is running
659    *  4. You cannot stop/schedule/start either
660    *
661    * This means you cannot use the API while doing live looped capture and we
662    * must resort to this.
663    *
664    * However, this is almost as efficient as event-based capture since it's ok
665    * to consistently overwait by a fixed amount; the extra bytes will just end
666    * up being used in the next call, and the extra latency will be constant. */
667   while (TRUE) {
668     hRes =
669         IDirectSoundCaptureBuffer_GetCurrentPosition (dsoundsrc->pDSBSecondary,
670         &dwCurrentCaptureCursor, NULL);
671 
672     if (FAILED (hRes)) {
673       GST_DSOUND_UNLOCK (dsoundsrc);
674       return -1;
675     }
676 
677     /* calculate the size of the buffer that's been captured while accounting
678      * for wrap-arounds */
679     if (dwCurrentCaptureCursor < dsoundsrc->current_circular_offset) {
680       dwBufferSize = dsoundsrc->buffer_size -
681           (dsoundsrc->current_circular_offset - dwCurrentCaptureCursor);
682     } else {
683       dwBufferSize =
684           dwCurrentCaptureCursor - dsoundsrc->current_circular_offset;
685     }
686 
687     if (dwBufferSize >= length) {
688       /* Yay, we got all the data we need */
689       break;
690     } else {
691       GST_DEBUG_OBJECT (asrc, "not enough data, got %lu (want at least %u)",
692           dwBufferSize, length);
693       /* If we didn't get enough data, sleep for a proportionate time */
694       sleep_time_ms = gst_util_uint64_scale (dsoundsrc->latency_time,
695           length - dwBufferSize, length * 1000);
696       /* Make sure we don't run in a tight loop unnecessarily */
697       sleep_time_ms = MAX (sleep_time_ms, 10);
698       /* Sleep using gst_clock_id_wait() so that we can be interrupted */
699       sleep_until = gst_clock_get_time (dsoundsrc->system_clock) +
700           sleep_time_ms * GST_MSECOND;
701       /* Setup the clock id wait */
702       if (G_UNLIKELY (dsoundsrc->read_wait_clock_id == NULL ||
703               gst_clock_single_shot_id_reinit (dsoundsrc->system_clock,
704                   dsoundsrc->read_wait_clock_id, sleep_until) == FALSE)) {
705         if (dsoundsrc->read_wait_clock_id != NULL)
706           gst_clock_id_unref (dsoundsrc->read_wait_clock_id);
707         dsoundsrc->read_wait_clock_id =
708             gst_clock_new_single_shot_id (dsoundsrc->system_clock, sleep_until);
709       }
710 
711       clock_id = dsoundsrc->read_wait_clock_id;
712       dsoundsrc->reset_while_sleeping = FALSE;
713 
714       GST_DEBUG_OBJECT (asrc, "waiting %" G_GUINT64_FORMAT "ms for more data",
715           sleep_time_ms);
716       GST_DSOUND_UNLOCK (dsoundsrc);
717 
718       gst_clock_id_wait (clock_id, NULL);
719 
720       GST_DSOUND_LOCK (dsoundsrc);
721 
722       if (dsoundsrc->reset_while_sleeping == TRUE) {
723         GST_DEBUG_OBJECT (asrc, "reset while sleeping, cancelled read");
724         GST_DSOUND_UNLOCK (dsoundsrc);
725         return -1;
726       }
727     }
728   }
729 
730   GST_DEBUG_OBJECT (asrc, "Got enough data: %lu bytes (wanted at least %u)",
731       dwBufferSize, length);
732 
733   /* Lock the buffer and read only the first @length bytes. Keep the rest in
734    * the capture buffer for the next read. */
735   hRes = IDirectSoundCaptureBuffer_Lock (dsoundsrc->pDSBSecondary,
736       dsoundsrc->current_circular_offset,
737       length,
738       &pLockedBuffer1, &dwSizeBuffer1, &pLockedBuffer2, &dwSizeBuffer2, 0L);
739 
740   /* NOTE: We now assume that dwSizeBuffer1 + dwSizeBuffer2 == length since the
741    * API is supposed to guarantee that */
742 
743   /* Copy buffer data to another buffer */
744   if (hRes == DS_OK) {
745     memcpy (data, pLockedBuffer1, dwSizeBuffer1);
746   }
747 
748   /* ...and if something is in another buffer */
749   if (pLockedBuffer2 != NULL) {
750     memcpy (((guchar *) data + dwSizeBuffer1), pLockedBuffer2, dwSizeBuffer2);
751   }
752 
753   dsoundsrc->current_circular_offset += dwSizeBuffer1 + dwSizeBuffer2;
754   dsoundsrc->current_circular_offset %= dsoundsrc->buffer_size;
755 
756   IDirectSoundCaptureBuffer_Unlock (dsoundsrc->pDSBSecondary,
757       pLockedBuffer1, dwSizeBuffer1, pLockedBuffer2, dwSizeBuffer2);
758 
759   GST_DSOUND_UNLOCK (dsoundsrc);
760 
761   /* We always read exactly @length data */
762   return length;
763 }
764 
765 static guint
gst_directsound_src_delay(GstAudioSrc * asrc)766 gst_directsound_src_delay (GstAudioSrc * asrc)
767 {
768   GstDirectSoundSrc *dsoundsrc;
769   HRESULT hRes;
770   DWORD dwCurrentCaptureCursor;
771   DWORD dwBytesInQueue = 0;
772   gint nNbSamplesInQueue = 0;
773 
774   GST_INFO_OBJECT (asrc, "Delay");
775 
776   dsoundsrc = GST_DIRECTSOUND_SRC (asrc);
777 
778   /* evaluate the number of samples in queue in the circular buffer */
779   hRes =
780       IDirectSoundCaptureBuffer_GetCurrentPosition (dsoundsrc->pDSBSecondary,
781       &dwCurrentCaptureCursor, NULL);
782   /* FIXME: Check is this calculated right */
783   if (hRes == S_OK) {
784     if (dwCurrentCaptureCursor < dsoundsrc->current_circular_offset) {
785       dwBytesInQueue =
786           dsoundsrc->buffer_size - (dsoundsrc->current_circular_offset -
787           dwCurrentCaptureCursor);
788     } else {
789       dwBytesInQueue =
790           dwCurrentCaptureCursor - dsoundsrc->current_circular_offset;
791     }
792 
793     nNbSamplesInQueue = dwBytesInQueue / dsoundsrc->bytes_per_sample;
794   }
795 
796   GST_INFO_OBJECT (asrc, "Delay is %d samples", nNbSamplesInQueue);
797 
798   return nNbSamplesInQueue;
799 }
800 
801 static void
gst_directsound_src_reset(GstAudioSrc * asrc)802 gst_directsound_src_reset (GstAudioSrc * asrc)
803 {
804   GstDirectSoundSrc *dsoundsrc;
805   LPVOID pLockedBuffer = NULL;
806   DWORD dwSizeBuffer = 0;
807 
808   GST_DEBUG_OBJECT (asrc, "reset directsoundsrc");
809 
810   dsoundsrc = GST_DIRECTSOUND_SRC (asrc);
811 
812   GST_DSOUND_LOCK (dsoundsrc);
813 
814   dsoundsrc->reset_while_sleeping = TRUE;
815   /* Interrupt read sleep if required */
816   if (dsoundsrc->read_wait_clock_id != NULL)
817     gst_clock_id_unschedule (dsoundsrc->read_wait_clock_id);
818 
819   if (dsoundsrc->pDSBSecondary) {
820     /*stop capturing */
821     HRESULT hRes = IDirectSoundCaptureBuffer_Stop (dsoundsrc->pDSBSecondary);
822 
823     /*reset position */
824     /*    hRes = IDirectSoundCaptureBuffer_SetCurrentPosition (dsoundsrc->pDSBSecondary, 0); */
825 
826     /*reset the buffer */
827     hRes = IDirectSoundCaptureBuffer_Lock (dsoundsrc->pDSBSecondary,
828         dsoundsrc->current_circular_offset, dsoundsrc->buffer_size,
829         &pLockedBuffer, &dwSizeBuffer, NULL, NULL, 0L);
830 
831     if (SUCCEEDED (hRes)) {
832       memset (pLockedBuffer, 0, dwSizeBuffer);
833 
834       hRes =
835           IDirectSoundCaptureBuffer_Unlock (dsoundsrc->pDSBSecondary,
836           pLockedBuffer, dwSizeBuffer, NULL, 0);
837     }
838     dsoundsrc->current_circular_offset = 0;
839 
840   }
841 
842   GST_DSOUND_UNLOCK (dsoundsrc);
843 }
844 
845 /* If the PROP_DEVICE_NAME is set, find the mixer related to device;
846  * otherwise we get the default input mixer. */
847 static gboolean
gst_directsound_src_mixer_find(GstDirectSoundSrc * dsoundsrc,MIXERCAPS * mixer_caps)848 gst_directsound_src_mixer_find (GstDirectSoundSrc * dsoundsrc,
849     MIXERCAPS * mixer_caps)
850 {
851   MMRESULT mmres;
852   guint i, num_mixers;
853 
854   num_mixers = mixerGetNumDevs ();
855   for (i = 0; i < num_mixers; i++) {
856     mmres = mixerOpen (&dsoundsrc->mixer, i, 0L, 0L,
857         MIXER_OBJECTF_MIXER | MIXER_OBJECTF_WAVEIN);
858 
859     if (mmres != MMSYSERR_NOERROR)
860       continue;
861 
862     mmres = mixerGetDevCaps ((UINT_PTR) dsoundsrc->mixer,
863         mixer_caps, sizeof (MIXERCAPS));
864 
865     if (mmres != MMSYSERR_NOERROR) {
866       mixerClose (dsoundsrc->mixer);
867       continue;
868     }
869 
870     /* Get default mixer */
871     if (dsoundsrc->device_name == NULL) {
872       GST_DEBUG ("Got default input mixer: %s", mixer_caps->szPname);
873       return TRUE;
874     }
875 
876     if (g_strstr_len (dsoundsrc->device_name, -1, mixer_caps->szPname) != NULL) {
877       GST_DEBUG ("Got requested input mixer: %s", mixer_caps->szPname);
878       return TRUE;
879     }
880 
881     /* Wrong mixer */
882     mixerClose (dsoundsrc->mixer);
883   }
884 
885   GST_DEBUG ("Can't find input mixer");
886   return FALSE;
887 }
888 
889 static void
gst_directsound_src_mixer_init(GstDirectSoundSrc * dsoundsrc)890 gst_directsound_src_mixer_init (GstDirectSoundSrc * dsoundsrc)
891 {
892   gint i, k;
893   gboolean found_mic;
894   MMRESULT mmres;
895   MIXERCAPS mixer_caps;
896   MIXERLINE mixer_line;
897   MIXERLINECONTROLS ml_ctrl;
898   PMIXERCONTROL pamixer_ctrls;
899 
900   if (!gst_directsound_src_mixer_find (dsoundsrc, &mixer_caps))
901     goto mixer_init_fail;
902 
903   /* Find the MIXERLINE related to MICROPHONE */
904   found_mic = FALSE;
905   for (i = 0; i < mixer_caps.cDestinations && !found_mic; i++) {
906     gint j, num_connections;
907 
908     mixer_line.cbStruct = sizeof (mixer_line);
909     mixer_line.dwDestination = i;
910     mmres = mixerGetLineInfo ((HMIXEROBJ) dsoundsrc->mixer,
911         &mixer_line, MIXER_GETLINEINFOF_DESTINATION);
912 
913     if (mmres != MMSYSERR_NOERROR)
914       goto mixer_init_fail;
915 
916     num_connections = mixer_line.cConnections;
917     for (j = 0; j < num_connections && !found_mic; j++) {
918       mixer_line.cbStruct = sizeof (mixer_line);
919       mixer_line.dwDestination = i;
920       mixer_line.dwSource = j;
921       mmres = mixerGetLineInfo ((HMIXEROBJ) dsoundsrc->mixer,
922           &mixer_line, MIXER_GETLINEINFOF_SOURCE);
923 
924       if (mmres != MMSYSERR_NOERROR)
925         goto mixer_init_fail;
926 
927       if (mixer_line.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE
928           || mixer_line.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_LINE)
929         found_mic = TRUE;
930     }
931   }
932 
933   if (found_mic == FALSE) {
934     GST_DEBUG ("Can't find mixer line related to input");
935     goto mixer_init_fail;
936   }
937 
938   /* Get control associated with microphone audio line */
939   pamixer_ctrls = g_malloc (sizeof (MIXERCONTROL) * mixer_line.cControls);
940   ml_ctrl.cbStruct = sizeof (ml_ctrl);
941   ml_ctrl.dwLineID = mixer_line.dwLineID;
942   ml_ctrl.cControls = mixer_line.cControls;
943   ml_ctrl.cbmxctrl = sizeof (MIXERCONTROL);
944   ml_ctrl.pamxctrl = pamixer_ctrls;
945   mmres = mixerGetLineControls ((HMIXEROBJ) dsoundsrc->mixer,
946       &ml_ctrl, MIXER_GETLINECONTROLSF_ALL);
947 
948   /* Find control associated with volume and mute */
949   for (k = 0; k < mixer_line.cControls; k++) {
950     if (strstr (pamixer_ctrls[k].szName, "Volume") != NULL) {
951       dsoundsrc->control_id_volume = pamixer_ctrls[k].dwControlID;
952       dsoundsrc->dw_vol_max = pamixer_ctrls[k].Bounds.dwMaximum;
953       dsoundsrc->dw_vol_min = pamixer_ctrls[k].Bounds.dwMinimum;
954     } else if (strstr (pamixer_ctrls[k].szName, "Mute") != NULL) {
955       dsoundsrc->control_id_mute = pamixer_ctrls[k].dwControlID;
956     } else {
957       GST_DEBUG ("Control not handled: %s", pamixer_ctrls[k].szName);
958     }
959   }
960   g_free (pamixer_ctrls);
961 
962   if (dsoundsrc->control_id_volume < 0 && dsoundsrc->control_id_mute < 0)
963     goto mixer_init_fail;
964 
965   /* Save cChannels information to properly changes in volume */
966   dsoundsrc->mixerline_cchannels = mixer_line.cChannels;
967   return;
968 
969 mixer_init_fail:
970   GST_WARNING ("Failed to get Volume and Mute controls");
971   if (dsoundsrc->mixer != NULL) {
972     mixerClose (dsoundsrc->mixer);
973     dsoundsrc->mixer = NULL;
974   }
975 }
976 
977 static gdouble
gst_directsound_src_get_volume(GstDirectSoundSrc * dsoundsrc)978 gst_directsound_src_get_volume (GstDirectSoundSrc * dsoundsrc)
979 {
980   return (gdouble) dsoundsrc->volume / 100;
981 }
982 
983 static gboolean
gst_directsound_src_get_mute(GstDirectSoundSrc * dsoundsrc)984 gst_directsound_src_get_mute (GstDirectSoundSrc * dsoundsrc)
985 {
986   return dsoundsrc->mute;
987 }
988 
989 static void
gst_directsound_src_set_volume(GstDirectSoundSrc * dsoundsrc,gdouble volume)990 gst_directsound_src_set_volume (GstDirectSoundSrc * dsoundsrc, gdouble volume)
991 {
992   MMRESULT mmres;
993   MIXERCONTROLDETAILS details;
994   MIXERCONTROLDETAILS_UNSIGNED details_unsigned;
995   glong dwvolume;
996 
997   if (dsoundsrc->mixer == NULL || dsoundsrc->control_id_volume < 0) {
998     GST_WARNING ("mixer not initialized");
999     return;
1000   }
1001 
1002   dwvolume = volume * dsoundsrc->dw_vol_max;
1003   dwvolume = CLAMP (dwvolume, dsoundsrc->dw_vol_min, dsoundsrc->dw_vol_max);
1004 
1005   GST_DEBUG ("max volume %ld | min volume %ld",
1006       dsoundsrc->dw_vol_max, dsoundsrc->dw_vol_min);
1007   GST_DEBUG ("set volume to %f (%ld)", volume, dwvolume);
1008 
1009   details.cbStruct = sizeof (details);
1010   details.dwControlID = dsoundsrc->control_id_volume;
1011   details.cChannels = dsoundsrc->mixerline_cchannels;
1012   details.cMultipleItems = 0;
1013 
1014   details_unsigned.dwValue = dwvolume;
1015   details.cbDetails = sizeof (MIXERCONTROLDETAILS_UNSIGNED);
1016   details.paDetails = &details_unsigned;
1017 
1018   mmres = mixerSetControlDetails ((HMIXEROBJ) dsoundsrc->mixer,
1019       &details, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE);
1020 
1021   if (mmres != MMSYSERR_NOERROR)
1022     GST_WARNING ("Failed to set volume");
1023   else
1024     dsoundsrc->volume = volume * 100;
1025 }
1026 
1027 static void
gst_directsound_src_set_mute(GstDirectSoundSrc * dsoundsrc,gboolean mute)1028 gst_directsound_src_set_mute (GstDirectSoundSrc * dsoundsrc, gboolean mute)
1029 {
1030   MMRESULT mmres;
1031   MIXERCONTROLDETAILS details;
1032   MIXERCONTROLDETAILS_BOOLEAN details_boolean;
1033 
1034   if (dsoundsrc->mixer == NULL || dsoundsrc->control_id_mute < 0) {
1035     GST_WARNING ("mixer not initialized");
1036     return;
1037   }
1038 
1039   details.cbStruct = sizeof (details);
1040   details.dwControlID = dsoundsrc->control_id_mute;
1041   details.cChannels = dsoundsrc->mixerline_cchannels;
1042   details.cMultipleItems = 0;
1043 
1044   details_boolean.fValue = mute;
1045   details.cbDetails = sizeof (MIXERCONTROLDETAILS_BOOLEAN);
1046   details.paDetails = &details_boolean;
1047 
1048   mmres = mixerSetControlDetails ((HMIXEROBJ) dsoundsrc->mixer,
1049       &details, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE);
1050 
1051   if (mmres != MMSYSERR_NOERROR)
1052     GST_WARNING ("Failed to set mute");
1053   else
1054     dsoundsrc->mute = mute;
1055 }
1056 
1057 static const gchar *
gst_directsound_src_get_device(GstDirectSoundSrc * dsoundsrc)1058 gst_directsound_src_get_device (GstDirectSoundSrc * dsoundsrc)
1059 {
1060   return dsoundsrc->device_id;
1061 }
1062 
1063 static void
gst_directsound_src_set_device(GstDirectSoundSrc * dsoundsrc,const gchar * device_id)1064 gst_directsound_src_set_device (GstDirectSoundSrc * dsoundsrc,
1065     const gchar * device_id)
1066 {
1067   g_free (dsoundsrc->device_id);
1068   dsoundsrc->device_id = g_strdup (device_id);
1069 }
1070