Tizen 2.0 Release
[framework/multimedia/gst-plugins-good0.10.git] / sys / waveform / gstwaveformsink.c
1 /* GStreamer
2 * Copyright (C) 2005 Sebastien Moutte <sebastien@moutte.net>
3 *
4 * gstwaveformsink.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 /**
23  * SECTION:element-waveformsink
24  *
25  * This element lets you output sound using the Windows WaveForm API.
26  *
27  * Note that you should almost always use generic audio conversion elements
28  * like audioconvert and audioresample in front of an audiosink to make sure
29  * your pipeline works under all circumstances (those conversion elements will
30  * act in passthrough-mode if no conversion is necessary).
31  *
32  * <refsect2>
33  * <title>Example pipelines</title>
34  * |[
35  * gst-launch -v audiotestsrc ! audioconvert ! volume volume=0.1 ! waveformsink
36  * ]| will output a sine wave (continuous beep sound) to your sound card (with
37  * a very low volume as precaution).
38  * |[
39  * gst-launch -v filesrc location=music.ogg ! decodebin ! audioconvert ! audioresample ! waveformsink
40  * ]| will play an Ogg/Vorbis audio file and output it.
41  * </refsect2>
42  */
43
44 #ifdef HAVE_CONFIG_H
45 #include "config.h"
46 #endif
47
48 #include "gstwaveformsink.h"
49
50 GST_DEBUG_CATEGORY_STATIC (waveformsink_debug);
51
52 static void gst_waveform_sink_base_init (gpointer g_class);
53 static void gst_waveform_sink_class_init (GstWaveFormSinkClass * klass);
54 static void gst_waveform_sink_init (GstWaveFormSink * wfsink,
55     GstWaveFormSinkClass * g_class);
56 static void gst_waveform_sink_finalise (GObject * object);
57 static void gst_waveform_sink_set_property (GObject * object,
58     guint prop_id, const GValue * value, GParamSpec * pspec);
59 static void gst_waveform_sink_get_property (GObject * object,
60     guint prop_id, GValue * value, GParamSpec * pspec);
61 static GstCaps *gst_waveform_sink_getcaps (GstBaseSink * bsink);
62
63 /************************************************************************/
64 /* GstAudioSink functions                                               */
65 /************************************************************************/
66 static gboolean gst_waveform_sink_prepare (GstAudioSink * asink,
67     GstRingBufferSpec * spec);
68 static gboolean gst_waveform_sink_unprepare (GstAudioSink * asink);
69 static gboolean gst_waveform_sink_open (GstAudioSink * asink);
70 static gboolean gst_waveform_sink_close (GstAudioSink * asink);
71 static guint gst_waveform_sink_write (GstAudioSink * asink, gpointer data,
72     guint length);
73 static guint gst_waveform_sink_delay (GstAudioSink * asink);
74 static void gst_waveform_sink_reset (GstAudioSink * asink);
75
76 /************************************************************************/
77 /* Utils                                                                */
78 /************************************************************************/
79 GstCaps *gst_waveform_sink_create_caps (gint rate, gint channels,
80     gint bits_per_sample);
81 WAVEHDR *bufferpool_get_buffer (GstWaveFormSink * wfsink, gpointer data,
82     guint length);
83 void CALLBACK waveOutProc (HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
84     DWORD_PTR dwParam1, DWORD_PTR dwParam2);
85
86 static GstStaticPadTemplate waveformsink_sink_factory =
87     GST_STATIC_PAD_TEMPLATE ("sink",
88     GST_PAD_SINK,
89     GST_PAD_ALWAYS,
90     GST_STATIC_CAPS ("audio/x-raw-int, "
91         "signed = (boolean) { TRUE, FALSE }, "
92         "width = (int) 16, "
93         "depth = (int) 16, "
94         "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ]; "
95         "audio/x-raw-int, "
96         "signed = (boolean) { TRUE, FALSE }, "
97         "width = (int) 8, "
98         "depth = (int) 8, "
99         "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ]"));
100
101 GST_BOILERPLATE (GstWaveFormSink, gst_waveform_sink, GstAudioSink,
102     GST_TYPE_AUDIO_SINK);
103
104 static void
105 gst_waveform_sink_base_init (gpointer g_class)
106 {
107   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
108
109   gst_element_class_set_details_simple (element_class, "WaveForm Audio Sink",
110       "Sink/Audio",
111       "Output to a sound card via WaveForm API",
112       "Sebastien Moutte <sebastien@moutte.net>");
113   gst_element_class_add_static_pad_template (element_class,
114       &waveformsink_sink_factory);
115 }
116
117 static void
118 gst_waveform_sink_class_init (GstWaveFormSinkClass * klass)
119 {
120   GObjectClass *gobject_class;
121   GstElementClass *gstelement_class;
122   GstBaseSinkClass *gstbasesink_class;
123   GstBaseAudioSinkClass *gstbaseaudiosink_class;
124   GstAudioSinkClass *gstaudiosink_class;
125
126   gobject_class = (GObjectClass *) klass;
127   gstelement_class = (GstElementClass *) klass;
128   gstbasesink_class = (GstBaseSinkClass *) klass;
129   gstbaseaudiosink_class = (GstBaseAudioSinkClass *) klass;
130   gstaudiosink_class = (GstAudioSinkClass *) klass;
131
132   parent_class = g_type_class_peek_parent (klass);
133
134   gobject_class->finalize = gst_waveform_sink_finalise;
135   gobject_class->get_property = gst_waveform_sink_get_property;
136   gobject_class->set_property = gst_waveform_sink_set_property;
137
138   gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_waveform_sink_getcaps);
139
140   gstaudiosink_class->prepare = GST_DEBUG_FUNCPTR (gst_waveform_sink_prepare);
141   gstaudiosink_class->unprepare =
142       GST_DEBUG_FUNCPTR (gst_waveform_sink_unprepare);
143   gstaudiosink_class->open = GST_DEBUG_FUNCPTR (gst_waveform_sink_open);
144   gstaudiosink_class->close = GST_DEBUG_FUNCPTR (gst_waveform_sink_close);
145   gstaudiosink_class->write = GST_DEBUG_FUNCPTR (gst_waveform_sink_write);
146   gstaudiosink_class->delay = GST_DEBUG_FUNCPTR (gst_waveform_sink_delay);
147   gstaudiosink_class->reset = GST_DEBUG_FUNCPTR (gst_waveform_sink_reset);
148
149   GST_DEBUG_CATEGORY_INIT (waveformsink_debug, "waveformsink", 0,
150       "Waveform sink");
151 }
152
153 static void
154 gst_waveform_sink_set_property (GObject * object, guint prop_id,
155     const GValue * value, GParamSpec * pspec)
156 {
157   GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (object);
158
159   switch (prop_id) {
160     default:
161       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
162       break;
163   }
164 }
165
166 static void
167 gst_waveform_sink_get_property (GObject * object, guint prop_id,
168     GValue * value, GParamSpec * pspec)
169 {
170   GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (object);
171
172   switch (prop_id) {
173     default:
174       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
175       break;
176   }
177 }
178
179 static void
180 gst_waveform_sink_init (GstWaveFormSink * wfsink,
181     GstWaveFormSinkClass * g_class)
182 {
183   /* initialize members */
184   wfsink->hwaveout = NULL;
185   wfsink->cached_caps = NULL;
186   wfsink->wave_buffers = NULL;
187   wfsink->write_buffer = 0;
188   wfsink->buffer_count = BUFFER_COUNT;
189   wfsink->buffer_size = BUFFER_SIZE;
190   wfsink->free_buffers_count = wfsink->buffer_count;
191   wfsink->bytes_in_queue = 0;
192
193   InitializeCriticalSection (&wfsink->critic_wave);
194 }
195
196 static void
197 gst_waveform_sink_finalise (GObject * object)
198 {
199   GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (object);
200
201   if (wfsink->cached_caps) {
202     gst_caps_unref (wfsink->cached_caps);
203     wfsink->cached_caps = NULL;
204   }
205
206   DeleteCriticalSection (&wfsink->critic_wave);
207
208   G_OBJECT_CLASS (parent_class)->finalize (object);
209 }
210
211 static GstCaps *
212 gst_waveform_sink_getcaps (GstBaseSink * bsink)
213 {
214   GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (bsink);
215   MMRESULT mmresult;
216   WAVEOUTCAPS wocaps;
217   GstCaps *caps, *caps_temp;
218
219   /* return the cached caps if already defined */
220   if (wfsink->cached_caps) {
221     return gst_caps_ref (wfsink->cached_caps);
222   }
223
224   /* get the default device caps */
225   mmresult = waveOutGetDevCaps (WAVE_MAPPER, &wocaps, sizeof (wocaps));
226   if (mmresult != MMSYSERR_NOERROR) {
227     waveOutGetErrorText (mmresult, wfsink->error_string, ERROR_LENGTH - 1);
228     GST_ELEMENT_ERROR (wfsink, RESOURCE, SETTINGS,
229         ("gst_waveform_sink_getcaps: waveOutGetDevCaps failed error=>%s",
230             wfsink->error_string), (NULL));
231     return NULL;
232   }
233
234   caps = gst_caps_new_empty ();
235
236   /* create a caps for all wave formats supported by the device 
237      starting by the best quality format */
238   if (wocaps.dwFormats & WAVE_FORMAT_96S16) {
239     caps_temp = gst_waveform_sink_create_caps (96000, 2, 16);
240     if (caps_temp) {
241       gst_caps_append (caps, caps_temp);
242     }
243   }
244   if (wocaps.dwFormats & WAVE_FORMAT_96S08) {
245     caps_temp = gst_waveform_sink_create_caps (96000, 2, 8);
246     if (caps_temp) {
247       gst_caps_append (caps, caps_temp);
248     }
249   }
250   if (wocaps.dwFormats & WAVE_FORMAT_96M16) {
251     caps_temp = gst_waveform_sink_create_caps (96000, 1, 16);
252     if (caps_temp) {
253       gst_caps_append (caps, caps_temp);
254     }
255   }
256   if (wocaps.dwFormats & WAVE_FORMAT_96M08) {
257     caps_temp = gst_waveform_sink_create_caps (96000, 1, 8);
258     if (caps_temp) {
259       gst_caps_append (caps, caps_temp);
260     }
261   }
262   if (wocaps.dwFormats & WAVE_FORMAT_4S16) {
263     caps_temp = gst_waveform_sink_create_caps (44100, 2, 16);
264     if (caps_temp) {
265       gst_caps_append (caps, caps_temp);
266     }
267   }
268   if (wocaps.dwFormats & WAVE_FORMAT_4S08) {
269     caps_temp = gst_waveform_sink_create_caps (44100, 2, 8);
270     if (caps_temp) {
271       gst_caps_append (caps, caps_temp);
272     }
273   }
274   if (wocaps.dwFormats & WAVE_FORMAT_4M16) {
275     caps_temp = gst_waveform_sink_create_caps (44100, 1, 16);
276     if (caps_temp) {
277       gst_caps_append (caps, caps_temp);
278     }
279   }
280   if (wocaps.dwFormats & WAVE_FORMAT_4M08) {
281     caps_temp = gst_waveform_sink_create_caps (44100, 1, 8);
282     if (caps_temp) {
283       gst_caps_append (caps, caps_temp);
284     }
285   }
286   if (wocaps.dwFormats & WAVE_FORMAT_2S16) {
287     caps_temp = gst_waveform_sink_create_caps (22050, 2, 16);
288     if (caps_temp) {
289       gst_caps_append (caps, caps_temp);
290     }
291   }
292   if (wocaps.dwFormats & WAVE_FORMAT_2S08) {
293     caps_temp = gst_waveform_sink_create_caps (22050, 2, 8);
294     if (caps_temp) {
295       gst_caps_append (caps, caps_temp);
296     }
297   }
298   if (wocaps.dwFormats & WAVE_FORMAT_2M16) {
299     caps_temp = gst_waveform_sink_create_caps (22050, 1, 16);
300     if (caps_temp) {
301       gst_caps_append (caps, caps_temp);
302     }
303   }
304   if (wocaps.dwFormats & WAVE_FORMAT_2M08) {
305     caps_temp = gst_waveform_sink_create_caps (22050, 1, 8);
306     if (caps_temp) {
307       gst_caps_append (caps, caps_temp);
308     }
309   }
310   if (wocaps.dwFormats & WAVE_FORMAT_1S16) {
311     caps_temp = gst_waveform_sink_create_caps (11025, 2, 16);
312     if (caps_temp) {
313       gst_caps_append (caps, caps_temp);
314     }
315   }
316   if (wocaps.dwFormats & WAVE_FORMAT_1S08) {
317     caps_temp = gst_waveform_sink_create_caps (11025, 2, 8);
318     if (caps_temp) {
319       gst_caps_append (caps, caps_temp);
320     }
321   }
322   if (wocaps.dwFormats & WAVE_FORMAT_1M16) {
323     caps_temp = gst_waveform_sink_create_caps (11025, 1, 16);
324     if (caps_temp) {
325       gst_caps_append (caps, caps_temp);
326     }
327   }
328   if (wocaps.dwFormats & WAVE_FORMAT_1M08) {
329     caps_temp = gst_waveform_sink_create_caps (11025, 1, 8);
330     if (caps_temp) {
331       gst_caps_append (caps, caps_temp);
332     }
333   }
334
335   if (gst_caps_is_empty (caps)) {
336     gst_caps_unref (caps);
337     caps = NULL;
338   } else {
339     wfsink->cached_caps = gst_caps_ref (caps);
340   }
341
342   GST_CAT_LOG_OBJECT (waveformsink_debug, wfsink, "Returning caps %s",
343       gst_caps_to_string (caps));
344
345   return caps;
346 }
347
348 static gboolean
349 gst_waveform_sink_open (GstAudioSink * asink)
350 {
351   GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (asink);
352
353   /* nothing to do here as the device needs to be opened with the format we will use */
354
355   return TRUE;
356 }
357
358 static gboolean
359 gst_waveform_sink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec)
360 {
361   GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (asink);
362   WAVEFORMATEX wfx;
363   MMRESULT mmresult;
364   guint index;
365
366   /* setup waveformex struture with the input ringbuffer specs */
367   memset (&wfx, 0, sizeof (wfx));
368   wfx.cbSize = 0;
369   wfx.wFormatTag = WAVE_FORMAT_PCM;
370   wfx.nChannels = spec->channels;
371   wfx.nSamplesPerSec = spec->rate;
372   wfx.wBitsPerSample = (spec->bytes_per_sample * 8) / wfx.nChannels;
373   wfx.nBlockAlign = spec->bytes_per_sample;
374   wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
375
376   /* save bytes per sample to use it in delay */
377   wfsink->bytes_per_sample = spec->bytes_per_sample;
378
379   /* open the default audio device with the given caps */
380   mmresult = waveOutOpen (&wfsink->hwaveout, WAVE_MAPPER,
381       &wfx, (DWORD_PTR) waveOutProc, (DWORD_PTR) wfsink, CALLBACK_FUNCTION);
382   if (mmresult != MMSYSERR_NOERROR) {
383     waveOutGetErrorText (mmresult, wfsink->error_string, ERROR_LENGTH - 1);
384     GST_ELEMENT_ERROR (wfsink, RESOURCE, OPEN_WRITE,
385         ("gst_waveform_sink_prepare: waveOutOpen failed error=>%s",
386             wfsink->error_string), (NULL));
387     return FALSE;
388   }
389
390   /* evaluate the buffer size and the number of buffers needed */
391   wfsink->free_buffers_count = wfsink->buffer_count;
392
393   /* allocate wave buffers */
394   wfsink->wave_buffers = (WAVEHDR *) g_new0 (WAVEHDR, wfsink->buffer_count);
395   if (!wfsink->wave_buffers) {
396     GST_ELEMENT_ERROR (wfsink, RESOURCE, OPEN_WRITE,
397         ("gst_waveform_sink_prepare: Failed to allocate wave buffer headers (buffer count=%d)",
398             wfsink->buffer_count), (NULL));
399     return FALSE;
400   }
401   memset (wfsink->wave_buffers, 0, sizeof (WAVEHDR) * wfsink->buffer_count);
402
403   /* setup headers */
404   for (index = 0; index < wfsink->buffer_count; index++) {
405     wfsink->wave_buffers[index].dwBufferLength = wfsink->buffer_size;
406     wfsink->wave_buffers[index].lpData = g_new0 (gchar, wfsink->buffer_size);
407   }
408
409   return TRUE;
410 }
411
412 static gboolean
413 gst_waveform_sink_unprepare (GstAudioSink * asink)
414 {
415   GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (asink);
416
417   /* free wave buffers */
418   if (wfsink->wave_buffers) {
419     guint index;
420
421     for (index = 0; index < wfsink->buffer_count; index++) {
422       if (wfsink->wave_buffers[index].dwFlags & WHDR_PREPARED) {
423         MMRESULT mmresult = waveOutUnprepareHeader (wfsink->hwaveout,
424             &wfsink->wave_buffers[index], sizeof (WAVEHDR));
425         if (mmresult != MMSYSERR_NOERROR) {
426           waveOutGetErrorText (mmresult, wfsink->error_string,
427               ERROR_LENGTH - 1);
428           GST_CAT_WARNING_OBJECT (waveformsink_debug, wfsink,
429               "gst_waveform_sink_unprepare: Error unpreparing buffer => %s",
430               wfsink->error_string);
431         }
432       }
433       g_free (wfsink->wave_buffers[index].lpData);
434     }
435     g_free (wfsink->wave_buffers);
436     wfsink->wave_buffers = NULL;
437   }
438
439   /* close waveform-audio output device */
440   if (wfsink->hwaveout) {
441     waveOutClose (wfsink->hwaveout);
442     wfsink->hwaveout = NULL;
443   }
444
445   return TRUE;
446 }
447
448 static gboolean
449 gst_waveform_sink_close (GstAudioSink * asink)
450 {
451   GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (asink);
452
453   return TRUE;
454 }
455
456 static guint
457 gst_waveform_sink_write (GstAudioSink * asink, gpointer data, guint length)
458 {
459   GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (asink);
460   WAVEHDR *waveheader;
461   MMRESULT mmresult;
462   guint bytes_to_write = length;
463   guint remaining_length = length;
464
465   wfsink->bytes_in_queue += length;
466
467   while (remaining_length > 0) {
468     if (wfsink->free_buffers_count == 0) {
469       /* no free buffer available, wait for one */
470       Sleep (10);
471       continue;
472     }
473
474     /* get the current write buffer header */
475     waveheader = &wfsink->wave_buffers[wfsink->write_buffer];
476
477     /* unprepare the header if needed */
478     if (waveheader->dwFlags & WHDR_PREPARED) {
479       mmresult =
480           waveOutUnprepareHeader (wfsink->hwaveout, waveheader,
481           sizeof (WAVEHDR));
482       if (mmresult != MMSYSERR_NOERROR) {
483         waveOutGetErrorText (mmresult, wfsink->error_string, ERROR_LENGTH - 1);
484         GST_CAT_WARNING_OBJECT (waveformsink_debug, wfsink,
485             "Error unpreparing buffer => %s", wfsink->error_string);
486       }
487     }
488
489     if (wfsink->buffer_size - waveheader->dwUser >= remaining_length)
490       bytes_to_write = remaining_length;
491     else
492       bytes_to_write = wfsink->buffer_size - waveheader->dwUser;
493
494     memcpy (waveheader->lpData + waveheader->dwUser, data, bytes_to_write);
495     waveheader->dwUser += bytes_to_write;
496     remaining_length -= bytes_to_write;
497     data = (guint8 *) data + bytes_to_write;
498
499     if (waveheader->dwUser == wfsink->buffer_size) {
500       /* we have filled a buffer, let's prepare it and next write it to the device */
501       mmresult =
502           waveOutPrepareHeader (wfsink->hwaveout, waveheader, sizeof (WAVEHDR));
503       if (mmresult != MMSYSERR_NOERROR) {
504         waveOutGetErrorText (mmresult, wfsink->error_string, ERROR_LENGTH - 1);
505         GST_CAT_WARNING_OBJECT (waveformsink_debug, wfsink,
506             "gst_waveform_sink_write: Error preparing header => %s",
507             wfsink->error_string);
508       }
509       mmresult = waveOutWrite (wfsink->hwaveout, waveheader, sizeof (WAVEHDR));
510       if (mmresult != MMSYSERR_NOERROR) {
511         waveOutGetErrorText (mmresult, wfsink->error_string, ERROR_LENGTH - 1);
512         GST_CAT_WARNING_OBJECT (waveformsink_debug, wfsink,
513             "gst_waveform_sink_write: Error writting buffer to the device => %s",
514             wfsink->error_string);
515       }
516
517       EnterCriticalSection (&wfsink->critic_wave);
518       wfsink->free_buffers_count--;
519       LeaveCriticalSection (&wfsink->critic_wave);
520
521       wfsink->write_buffer++;
522       wfsink->write_buffer %= wfsink->buffer_count;
523       waveheader->dwUser = 0;
524       wfsink->bytes_in_queue = 0;
525       GST_CAT_LOG_OBJECT (waveformsink_debug, wfsink,
526           "gst_waveform_sink_write: Writting a buffer to the device (free buffers remaining=%d, write buffer=%d)",
527           wfsink->free_buffers_count, wfsink->write_buffer);
528     }
529   }
530
531   return length;
532 }
533
534 static guint
535 gst_waveform_sink_delay (GstAudioSink * asink)
536 {
537   /* return the number of samples in queue (device+internal queue) */
538   GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (asink);
539   guint bytes_in_device =
540       (wfsink->buffer_count - wfsink->free_buffers_count) * wfsink->buffer_size;
541   guint delay =
542       (bytes_in_device + wfsink->bytes_in_queue) / wfsink->bytes_per_sample;
543   return delay;
544 }
545
546 static void
547 gst_waveform_sink_reset (GstAudioSink * asink)
548 {
549   GstWaveFormSink *wfsink = GST_WAVEFORM_SINK (asink);
550   MMRESULT mmresult = waveOutReset (wfsink->hwaveout);
551
552   if (mmresult != MMSYSERR_NOERROR) {
553     waveOutGetErrorText (mmresult, wfsink->error_string, ERROR_LENGTH - 1);
554     GST_CAT_WARNING_OBJECT (waveformsink_debug, wfsink,
555         "gst_waveform_sink_reset: Error reseting waveform-audio device => %s",
556         wfsink->error_string);
557   }
558 }
559
560 GstCaps *
561 gst_waveform_sink_create_caps (gint rate, gint channels, gint bits_per_sample)
562 {
563   GstCaps *caps = NULL;
564
565   caps = gst_caps_new_simple ("audio/x-raw-int",
566       "width", G_TYPE_INT, bits_per_sample,
567       "depth", G_TYPE_INT, bits_per_sample,
568       "endianness", G_TYPE_INT, G_BYTE_ORDER,
569       "signed", G_TYPE_BOOLEAN, TRUE,
570       "channels", G_TYPE_INT, channels, "rate", G_TYPE_INT, rate, NULL);
571   return caps;
572 }
573
574 void CALLBACK
575 waveOutProc (HWAVEOUT hwo,
576     UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
577 {
578   GstWaveFormSink *wfsink = (GstWaveFormSink *) dwInstance;
579
580   if (uMsg == WOM_DONE) {
581     EnterCriticalSection (&wfsink->critic_wave);
582     wfsink->free_buffers_count++;
583     LeaveCriticalSection (&wfsink->critic_wave);
584   }
585 }