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