sys/directsound/gstdirectsoundsink.*: Add an attenuation property that will directly...
[platform/upstream/gst-plugins-good.git] / sys / directsound / gstdirectsoundsink.c
1 /* GStreamer
2 * Copyright (C) 2005 Sebastien Moutte <sebastien@moutte.net>
3 *
4 * gstdirectsoundsink.c:
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include "gstdirectsoundsink.h"
27
28 #include <fcntl.h>
29 #include <errno.h>
30 #include <unistd.h>
31 #include <string.h>
32
33 GST_DEBUG_CATEGORY_STATIC (directsoundsink_debug);
34
35 /* elementfactory information */
36 static const GstElementDetails gst_directsoundsink_details =
37 GST_ELEMENT_DETAILS ("Audio Sink (DIRECTSOUND)",
38     "Sink/Audio",
39     "Output to a sound card via DIRECTSOUND",
40     "Sebastien Moutte <sebastien@moutte.net>");
41
42 static void gst_directsoundsink_base_init (gpointer g_class);
43 static void gst_directsoundsink_class_init (GstDirectSoundSinkClass * klass);
44 static void gst_directsoundsink_init (GstDirectSoundSink * dsoundsink,
45     GstDirectSoundSinkClass * g_class);
46 static void gst_directsoundsink_dispose (GObject * object);
47 static void gst_directsoundsink_finalise (GObject * object);
48 static void gst_directsoundsink_set_property (GObject * object,
49     guint prop_id, const GValue * value, GParamSpec * pspec);
50 static void gst_directsoundsink_get_property (GObject * object,
51     guint prop_id, GValue * value, GParamSpec * pspec);
52
53 static GstCaps *gst_directsoundsink_getcaps (GstBaseSink * bsink);
54
55 static gboolean gst_directsoundsink_prepare (GstAudioSink * asink,
56     GstRingBufferSpec * spec);
57 static gboolean gst_directsoundsink_unprepare (GstAudioSink * asink);
58
59 static gboolean gst_directsoundsink_open (GstAudioSink * asink);
60 static gboolean gst_directsoundsink_close (GstAudioSink * asink);
61 static guint gst_directsoundsink_write (GstAudioSink * asink, gpointer data,
62     guint length);
63 static guint gst_directsoundsink_delay (GstAudioSink * asink);
64 static void gst_directsoundsink_reset (GstAudioSink * asink);
65
66
67 static GstStaticPadTemplate directsoundsink_sink_factory =
68     GST_STATIC_PAD_TEMPLATE ("sink",
69     GST_PAD_SINK,
70     GST_PAD_ALWAYS,
71     GST_STATIC_CAPS ("audio/x-raw-int, "
72         "endianness = (int) { LITTLE_ENDIAN, BIG_ENDIAN }, "
73         "signed = (boolean) { TRUE, FALSE }, "
74         "width = (int) 16, "
75         "depth = (int) 16, "
76         "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ]; "
77         "audio/x-raw-int, "
78         "signed = (boolean) { TRUE, FALSE }, "
79         "width = (int) 8, "
80         "depth = (int) 8, "
81         "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ]"));
82
83 enum
84 {
85   PROP_0,
86   PROP_ATTENUATION
87 };
88
89 static void
90 _do_init (GType directsoundsink_type)
91 {
92   GST_DEBUG_CATEGORY_INIT (directsoundsink_debug, "directsoundsink", 0,
93       "DirectSound sink");
94 }
95
96 GST_BOILERPLATE_FULL (GstDirectSoundSink, gst_directsoundsink, GstAudioSink,
97     GST_TYPE_AUDIO_SINK, _do_init);
98
99 static void
100 gst_directsoundsink_dispose (GObject * object)
101 {
102   G_OBJECT_CLASS (parent_class)->dispose (object);
103 }
104
105 static void
106 gst_directsoundsink_finalise (GObject * object)
107 {
108   GstDirectSoundSink *dsoundsink = GST_DIRECTSOUND_SINK (object);
109
110   g_mutex_free (dsoundsink->dsound_lock);
111
112   G_OBJECT_CLASS (parent_class)->finalize (object);
113 }
114
115 static void
116 gst_directsoundsink_base_init (gpointer g_class)
117 {
118   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
119
120   gst_element_class_set_details (element_class, &gst_directsoundsink_details);
121
122   gst_element_class_add_pad_template (element_class,
123       gst_static_pad_template_get (&directsoundsink_sink_factory));
124 }
125
126 static void
127 gst_directsoundsink_class_init (GstDirectSoundSinkClass * klass)
128 {
129   GObjectClass *gobject_class;
130   GstElementClass *gstelement_class;
131   GstBaseSinkClass *gstbasesink_class;
132   GstBaseAudioSinkClass *gstbaseaudiosink_class;
133   GstAudioSinkClass *gstaudiosink_class;
134
135   gobject_class = (GObjectClass *) klass;
136   gstelement_class = (GstElementClass *) klass;
137   gstbasesink_class = (GstBaseSinkClass *) klass;
138   gstbaseaudiosink_class = (GstBaseAudioSinkClass *) klass;
139   gstaudiosink_class = (GstAudioSinkClass *) klass;
140
141   parent_class = g_type_class_peek_parent (klass);
142
143   gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_directsoundsink_dispose);
144   gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_directsoundsink_finalise);
145   gobject_class->get_property =
146       GST_DEBUG_FUNCPTR (gst_directsoundsink_get_property);
147   gobject_class->set_property =
148       GST_DEBUG_FUNCPTR (gst_directsoundsink_set_property);
149
150   gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_directsoundsink_getcaps);
151
152   gstaudiosink_class->prepare = GST_DEBUG_FUNCPTR (gst_directsoundsink_prepare);
153   gstaudiosink_class->unprepare =
154       GST_DEBUG_FUNCPTR (gst_directsoundsink_unprepare);
155   gstaudiosink_class->open = GST_DEBUG_FUNCPTR (gst_directsoundsink_open);
156   gstaudiosink_class->close = GST_DEBUG_FUNCPTR (gst_directsoundsink_close);
157   gstaudiosink_class->write = GST_DEBUG_FUNCPTR (gst_directsoundsink_write);
158
159   gstaudiosink_class->delay = GST_DEBUG_FUNCPTR (gst_directsoundsink_delay);
160   gstaudiosink_class->reset = GST_DEBUG_FUNCPTR (gst_directsoundsink_reset);
161
162   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_ATTENUATION,
163       g_param_spec_long ("attenuation", "Attenuation of the sound",
164           "The attenuation for the directsound buffer (default is 0 so the directsound buffer will not be attenuated)",
165           -10000, 0, 0, G_PARAM_READWRITE));
166
167 }
168
169 static void
170 gst_directsoundsink_set_property (GObject * object, guint prop_id,
171     const GValue * value, GParamSpec * pspec)
172 {
173   GstDirectSoundSink *dsoundsink;
174
175   dsoundsink = GST_DIRECTSOUND_SINK (object);
176
177   switch (prop_id) {
178     case PROP_ATTENUATION:
179     {
180       glong attenuation = g_value_get_long (value);
181
182       if (attenuation != dsoundsink->attenuation) {
183         dsoundsink->attenuation = attenuation;
184
185         if (dsoundsink->pDSBSecondary)
186           IDirectSoundBuffer_SetVolume (dsoundsink->pDSBSecondary,
187               dsoundsink->attenuation);
188       }
189
190       break;
191     }
192     default:
193       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
194       break;
195   }
196 }
197
198 static void
199 gst_directsoundsink_get_property (GObject * object, guint prop_id,
200     GValue * value, GParamSpec * pspec)
201 {
202   GstDirectSoundSink *dsoundsink;
203
204   dsoundsink = GST_DIRECTSOUND_SINK (object);
205
206   switch (prop_id) {
207     case PROP_ATTENUATION:
208       g_value_set_long (value, dsoundsink->attenuation);
209       break;
210     default:
211       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
212       break;
213   }
214 }
215
216 static void
217 gst_directsoundsink_init (GstDirectSoundSink * dsoundsink,
218     GstDirectSoundSinkClass * g_class)
219 {
220   dsoundsink->pDS = NULL;
221   dsoundsink->pDSBSecondary = NULL;
222   dsoundsink->current_circular_offset = 0;
223   dsoundsink->buffer_size = DSBSIZE_MIN;
224   dsoundsink->attenuation = 0;
225   dsoundsink->dsound_lock = g_mutex_new ();
226   dsoundsink->first_buffer_after_reset = FALSE;
227 }
228
229 static GstCaps *
230 gst_directsoundsink_getcaps (GstBaseSink * bsink)
231 {
232   GstDirectSoundSink *dsoundsink;
233
234   dsoundsink = GST_DIRECTSOUND_SINK (bsink);
235
236   return
237       gst_caps_copy (gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD
238           (dsoundsink)));
239 }
240
241 static gboolean
242 gst_directsoundsink_open (GstAudioSink * asink)
243 {
244   GstDirectSoundSink *dsoundsink;
245   HRESULT hRes;
246
247   dsoundsink = GST_DIRECTSOUND_SINK (asink);
248
249   /* create and initialize a DirecSound object */
250   if (FAILED (hRes = DirectSoundCreate (NULL, &dsoundsink->pDS, NULL))) {
251     GST_ELEMENT_ERROR (dsoundsink, RESOURCE, OPEN_READ,
252         ("gst_directsoundsink_open: DirectSoundCreate: %s",
253             DXGetErrorString9 (hRes)), (NULL));
254     return FALSE;
255   }
256
257   if (FAILED (hRes = IDirectSound_SetCooperativeLevel (dsoundsink->pDS,
258               GetDesktopWindow (), DSSCL_PRIORITY))) {
259     GST_ELEMENT_ERROR (dsoundsink, RESOURCE, OPEN_READ,
260         ("gst_directsoundsink_open: IDirectSound_SetCooperativeLevel: %s",
261             DXGetErrorString9 (hRes)), (NULL));
262     return FALSE;
263   }
264
265   return TRUE;
266 }
267
268 static gboolean
269 gst_directsoundsink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec)
270 {
271   GstDirectSoundSink *dsoundsink;
272   HRESULT hRes;
273   DSBUFFERDESC descSecondary;
274   WAVEFORMATEX wfx;
275
276   dsoundsink = GST_DIRECTSOUND_SINK (asink);
277
278   /*save number of bytes per sample */
279   dsoundsink->bytes_per_sample = spec->bytes_per_sample;
280
281   /* fill the WAVEFORMATEX struture with spec params */
282   memset (&wfx, 0, sizeof (wfx));
283   wfx.cbSize = sizeof (wfx);
284   wfx.wFormatTag = WAVE_FORMAT_PCM;
285   wfx.nChannels = spec->channels;
286   wfx.nSamplesPerSec = spec->rate;
287   wfx.wBitsPerSample = (spec->bytes_per_sample * 8) / wfx.nChannels;
288   wfx.nBlockAlign = spec->bytes_per_sample;
289   wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
290
291   /* directsound buffer size can handle 1/2 sec of the stream */
292   dsoundsink->buffer_size = wfx.nAvgBytesPerSec / 2;
293
294   GST_CAT_INFO (directsoundsink_debug,
295       "GstRingBufferSpec->channels: %d, GstRingBufferSpec->rate: %d, GstRingBufferSpec->bytes_per_sample: %d\n"
296       "WAVEFORMATEX.nSamplesPerSec: %ld, WAVEFORMATEX.wBitsPerSample: %d, WAVEFORMATEX.nBlockAlign: %d, WAVEFORMATEX.nAvgBytesPerSec: %ld\n"
297       "Size of dsound cirucular buffe=>%d\n", spec->channels, spec->rate,
298       spec->bytes_per_sample, wfx.nSamplesPerSec, wfx.wBitsPerSample,
299       wfx.nBlockAlign, wfx.nAvgBytesPerSec, dsoundsink->buffer_size);
300
301   /* create a secondary directsound buffer */
302   memset (&descSecondary, 0, sizeof (DSBUFFERDESC));
303   descSecondary.dwSize = sizeof (DSBUFFERDESC);
304   descSecondary.dwFlags = DSBCAPS_GETCURRENTPOSITION2 |
305       DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLVOLUME;
306
307   descSecondary.dwBufferBytes = dsoundsink->buffer_size;
308   descSecondary.lpwfxFormat = (WAVEFORMATEX *) & wfx;
309
310   hRes = IDirectSound_CreateSoundBuffer (dsoundsink->pDS, &descSecondary,
311       &dsoundsink->pDSBSecondary, NULL);
312   if (FAILED (hRes)) {
313     GST_ELEMENT_ERROR (dsoundsink, RESOURCE, OPEN_READ,
314         ("gst_directsoundsink_prepare: IDirectSound_CreateSoundBuffer: %s",
315             DXGetErrorString9 (hRes)), (NULL));
316     return FALSE;
317   }
318
319   if (dsoundsink->attenuation != 0)
320     IDirectSoundBuffer_SetVolume (dsoundsink->pDSBSecondary,
321         dsoundsink->attenuation);
322
323   return TRUE;
324 }
325
326 static gboolean
327 gst_directsoundsink_unprepare (GstAudioSink * asink)
328 {
329   GstDirectSoundSink *dsoundsink;
330
331   dsoundsink = GST_DIRECTSOUND_SINK (asink);
332
333   /* release secondary DirectSound buffer */
334   if (dsoundsink->pDSBSecondary)
335     IDirectSoundBuffer_Release (dsoundsink->pDSBSecondary);
336
337   return TRUE;
338 }
339
340 static gboolean
341 gst_directsoundsink_close (GstAudioSink * asink)
342 {
343   GstDirectSoundSink *dsoundsink = NULL;
344
345   dsoundsink = GST_DIRECTSOUND_SINK (asink);
346
347   /* release DirectSound object */
348   g_return_val_if_fail (dsoundsink->pDS != NULL, FALSE);
349   IDirectSound_Release (dsoundsink->pDS);
350
351   return TRUE;
352 }
353
354
355 static guint
356 gst_directsoundsink_write (GstAudioSink * asink, gpointer data, guint length)
357 {
358   GstDirectSoundSink *dsoundsink;
359   DWORD dwStatus;
360   HRESULT hRes;
361   LPVOID pLockedBuffer1 = NULL, pLockedBuffer2 = NULL;
362   DWORD dwSizeBuffer1, dwSizeBuffer2;
363   DWORD dwCurrentPlayCursor;
364
365   dsoundsink = GST_DIRECTSOUND_SINK (asink);
366
367   GST_DSOUND_LOCK (dsoundsink);
368
369   /* get current buffer status */
370   hRes = IDirectSoundBuffer_GetStatus (dsoundsink->pDSBSecondary, &dwStatus);
371
372   /* get current play cursor position */
373   hRes = IDirectSoundBuffer_GetCurrentPosition (dsoundsink->pDSBSecondary,
374       &dwCurrentPlayCursor, NULL);
375
376   if (SUCCEEDED (hRes) && (dwStatus & DSBSTATUS_PLAYING)) {
377     DWORD dwFreeBufferSize;
378
379   calculate_freesize:
380     /* calculate the free size of the circular buffer */
381     if (dwCurrentPlayCursor < dsoundsink->current_circular_offset)
382       dwFreeBufferSize =
383           dsoundsink->buffer_size - (dsoundsink->current_circular_offset -
384           dwCurrentPlayCursor);
385     else
386       dwFreeBufferSize =
387           dwCurrentPlayCursor - dsoundsink->current_circular_offset;
388
389     if (length >= dwFreeBufferSize) {
390       Sleep (100);
391       hRes = IDirectSoundBuffer_GetCurrentPosition (dsoundsink->pDSBSecondary,
392           &dwCurrentPlayCursor, NULL);
393
394       hRes =
395           IDirectSoundBuffer_GetStatus (dsoundsink->pDSBSecondary, &dwStatus);
396       if (SUCCEEDED (hRes) && (dwStatus & DSBSTATUS_PLAYING))
397         goto calculate_freesize;
398       else {
399         dsoundsink->first_buffer_after_reset = FALSE;
400         GST_DSOUND_UNLOCK (dsoundsink);
401         return 0;
402       }
403     }
404   }
405
406   if (dwStatus & DSBSTATUS_BUFFERLOST) {
407     hRes = IDirectSoundBuffer_Restore (dsoundsink->pDSBSecondary);      /*need a loop waiting the buffer is restored?? */
408
409     dsoundsink->current_circular_offset = 0;
410   }
411
412   hRes = IDirectSoundBuffer_Lock (dsoundsink->pDSBSecondary,
413       dsoundsink->current_circular_offset, length, &pLockedBuffer1,
414       &dwSizeBuffer1, &pLockedBuffer2, &dwSizeBuffer2, 0L);
415
416   if (SUCCEEDED (hRes)) {
417     // Write to pointers without reordering.
418     memcpy (pLockedBuffer1, data, dwSizeBuffer1);
419     if (pLockedBuffer2 != NULL)
420       memcpy (pLockedBuffer2, (LPBYTE) data + dwSizeBuffer1, dwSizeBuffer2);
421
422     // Update where the buffer will lock (for next time)
423     dsoundsink->current_circular_offset += dwSizeBuffer1 + dwSizeBuffer2;
424     dsoundsink->current_circular_offset %= dsoundsink->buffer_size;     /* Circular buffer */
425
426     hRes = IDirectSoundBuffer_Unlock (dsoundsink->pDSBSecondary, pLockedBuffer1,
427         dwSizeBuffer1, pLockedBuffer2, dwSizeBuffer2);
428   }
429
430   /* if the buffer was not in playing state yet, call play on the buffer 
431      except if this buffer is the fist after a reset (base class call reset and write a buffer when setting the sink to pause) */
432   if (!(dwStatus & DSBSTATUS_PLAYING) &&
433       dsoundsink->first_buffer_after_reset == FALSE) {
434     hRes = IDirectSoundBuffer_Play (dsoundsink->pDSBSecondary, 0, 0,
435         DSBPLAY_LOOPING);
436   }
437
438   dsoundsink->first_buffer_after_reset = FALSE;
439
440   GST_DSOUND_UNLOCK (dsoundsink);
441
442   return length;
443 }
444
445 static guint
446 gst_directsoundsink_delay (GstAudioSink * asink)
447 {
448   GstDirectSoundSink *dsoundsink;
449   HRESULT hRes;
450   DWORD dwCurrentPlayCursor;
451   DWORD dwBytesInQueue = 0;
452   gint nNbSamplesInQueue = 0;
453   DWORD dwStatus;
454
455   dsoundsink = GST_DIRECTSOUND_SINK (asink);
456
457   /* get current buffer status */
458   hRes = IDirectSoundBuffer_GetStatus (dsoundsink->pDSBSecondary, &dwStatus);
459
460   if (dwStatus & DSBSTATUS_PLAYING) {
461     /*evaluate the number of samples in queue in the circular buffer */
462     hRes = IDirectSoundBuffer_GetCurrentPosition (dsoundsink->pDSBSecondary,
463         &dwCurrentPlayCursor, NULL);
464
465     if (hRes == S_OK) {
466       if (dwCurrentPlayCursor < dsoundsink->current_circular_offset)
467         dwBytesInQueue =
468             dsoundsink->current_circular_offset - dwCurrentPlayCursor;
469       else
470         dwBytesInQueue =
471             dsoundsink->current_circular_offset + (dsoundsink->buffer_size -
472             dwCurrentPlayCursor);
473
474       nNbSamplesInQueue = dwBytesInQueue / dsoundsink->bytes_per_sample;
475     }
476   }
477
478   return nNbSamplesInQueue;
479 }
480
481 static void
482 gst_directsoundsink_reset (GstAudioSink * asink)
483 {
484   /*not tested for seeking */
485   GstDirectSoundSink *dsoundsink;
486   LPBYTE pLockedBuffer = NULL;
487   DWORD dwSizeBuffer = 0;
488
489   dsoundsink = GST_DIRECTSOUND_SINK (asink);
490
491   GST_DSOUND_LOCK (dsoundsink);
492
493   if (dsoundsink->pDSBSecondary) {
494     /*stop playing */
495     HRESULT hRes = IDirectSoundBuffer_Stop (dsoundsink->pDSBSecondary);
496
497     /*reset position */
498     hRes = IDirectSoundBuffer_SetCurrentPosition (dsoundsink->pDSBSecondary, 0);
499     dsoundsink->current_circular_offset = 0;
500
501     /*reset the buffer */
502     hRes = IDirectSoundBuffer_Lock (dsoundsink->pDSBSecondary,
503         dsoundsink->current_circular_offset, dsoundsink->buffer_size,
504         &pLockedBuffer, &dwSizeBuffer, NULL, NULL, 0L);
505
506     if (SUCCEEDED (hRes)) {
507       memset (pLockedBuffer, 0, dwSizeBuffer);
508
509       hRes =
510           IDirectSoundBuffer_Unlock (dsoundsink->pDSBSecondary, pLockedBuffer,
511           dwSizeBuffer, NULL, 0);
512     }
513   }
514
515   dsoundsink->first_buffer_after_reset = TRUE;
516
517   GST_DSOUND_UNLOCK (dsoundsink);
518 }