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_value_dup_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 description */
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