]> code.delx.au - pulseaudio/blob - src/modules/module-waveout.c
Update PA_MODULE_USAGE to be in line with actual implementation
[pulseaudio] / src / modules / module-waveout.c
1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2006 Lennart Poettering
5 Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB
6
7 PulseAudio is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published
9 by the Free Software Foundation; either version 2.1 of the License,
10 or (at your option) any later version.
11
12 PulseAudio is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with PulseAudio; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 USA.
21 ***/
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <windows.h>
28 #include <mmsystem.h>
29
30 #include <pulse/mainloop-api.h>
31
32 #include <pulse/xmalloc.h>
33 #include <pulse/timeval.h>
34 #include <pulse/rtclock.h>
35
36 #include <pulsecore/sink.h>
37 #include <pulsecore/source.h>
38 #include <pulsecore/module.h>
39 #include <pulsecore/modargs.h>
40 #include <pulsecore/sample-util.h>
41 #include <pulsecore/core-util.h>
42 #include <pulsecore/log.h>
43 #include <pulsecore/thread.h>
44 #include <pulsecore/thread-mq.h>
45
46 #include "module-waveout-symdef.h"
47
48 PA_MODULE_AUTHOR("Pierre Ossman");
49 PA_MODULE_DESCRIPTION("Windows waveOut Sink/Source");
50 PA_MODULE_VERSION(PACKAGE_VERSION);
51 PA_MODULE_USAGE(
52 "sink_name=<name for the sink> "
53 "source_name=<name for the source> "
54 "device=<device number> "
55 "record=<enable source?> "
56 "playback=<enable sink?> "
57 "format=<sample format> "
58 "rate=<sample rate> "
59 "channels=<number of channels> "
60 "channel_map=<channel map> "
61 "fragments=<number of fragments> "
62 "fragment_size=<fragment size>");
63
64 #define DEFAULT_SINK_NAME "wave_output"
65 #define DEFAULT_SOURCE_NAME "wave_input"
66
67 #define WAVEOUT_MAX_VOLUME 0xFFFF
68
69 struct userdata {
70 pa_sink *sink;
71 pa_source *source;
72 pa_core *core;
73 pa_usec_t poll_timeout;
74
75 pa_thread *thread;
76 pa_thread_mq thread_mq;
77 pa_rtpoll *rtpoll;
78
79 uint32_t fragments, fragment_size;
80
81 uint32_t free_ofrags, free_ifrags;
82
83 DWORD written_bytes;
84 int sink_underflow;
85
86 int cur_ohdr, cur_ihdr;
87 WAVEHDR *ohdrs, *ihdrs;
88
89 HWAVEOUT hwo;
90 HWAVEIN hwi;
91 pa_module *module;
92
93 CRITICAL_SECTION crit;
94 };
95
96 static const char* const valid_modargs[] = {
97 "sink_name",
98 "source_name",
99 "device",
100 "record",
101 "playback",
102 "fragments",
103 "fragment_size",
104 "format",
105 "rate",
106 "channels",
107 "channel_map",
108 NULL
109 };
110
111 static void do_write(struct userdata *u) {
112 uint32_t free_frags;
113 pa_memchunk memchunk;
114 WAVEHDR *hdr;
115 MMRESULT res;
116 void *p;
117
118 if (!u->sink)
119 return;
120
121 if (!PA_SINK_IS_LINKED(u->sink->state))
122 return;
123
124 EnterCriticalSection(&u->crit);
125 free_frags = u->free_ofrags;
126 LeaveCriticalSection(&u->crit);
127
128 if (!u->sink_underflow && (free_frags == u->fragments))
129 pa_log_debug("WaveOut underflow!");
130
131 while (free_frags) {
132 hdr = &u->ohdrs[u->cur_ohdr];
133 if (hdr->dwFlags & WHDR_PREPARED)
134 waveOutUnprepareHeader(u->hwo, hdr, sizeof(WAVEHDR));
135
136 hdr->dwBufferLength = 0;
137 while (hdr->dwBufferLength < u->fragment_size) {
138 size_t len;
139
140 len = u->fragment_size - hdr->dwBufferLength;
141
142 pa_sink_render(u->sink, len, &memchunk);
143
144 pa_assert(memchunk.memblock);
145 pa_assert(memchunk.length);
146
147 if (memchunk.length < len)
148 len = memchunk.length;
149
150 p = pa_memblock_acquire(memchunk.memblock);
151 memcpy(hdr->lpData + hdr->dwBufferLength, (char*) p + memchunk.index, len);
152 pa_memblock_release(memchunk.memblock);
153
154 hdr->dwBufferLength += len;
155
156 pa_memblock_unref(memchunk.memblock);
157 memchunk.memblock = NULL;
158 }
159
160 /* Insufficient data in sink buffer? */
161 if (hdr->dwBufferLength == 0) {
162 u->sink_underflow = 1;
163 break;
164 }
165
166 u->sink_underflow = 0;
167
168 res = waveOutPrepareHeader(u->hwo, hdr, sizeof(WAVEHDR));
169 if (res != MMSYSERR_NOERROR)
170 pa_log_error("Unable to prepare waveOut block: %d", res);
171
172 res = waveOutWrite(u->hwo, hdr, sizeof(WAVEHDR));
173 if (res != MMSYSERR_NOERROR)
174 pa_log_error("Unable to write waveOut block: %d", res);
175
176 u->written_bytes += hdr->dwBufferLength;
177
178 EnterCriticalSection(&u->crit);
179 u->free_ofrags--;
180 LeaveCriticalSection(&u->crit);
181
182 free_frags--;
183 u->cur_ohdr++;
184 u->cur_ohdr %= u->fragments;
185 }
186 }
187
188 static void do_read(struct userdata *u) {
189 uint32_t free_frags;
190 pa_memchunk memchunk;
191 WAVEHDR *hdr;
192 MMRESULT res;
193 void *p;
194
195 if (!u->source)
196 return;
197
198 if (!PA_SOURCE_IS_LINKED(u->source->state))
199 return;
200
201 EnterCriticalSection(&u->crit);
202 free_frags = u->free_ifrags;
203 u->free_ifrags = 0;
204 LeaveCriticalSection(&u->crit);
205
206 if (free_frags == u->fragments)
207 pa_log_debug("WaveIn overflow!");
208
209 while (free_frags) {
210 hdr = &u->ihdrs[u->cur_ihdr];
211 if (hdr->dwFlags & WHDR_PREPARED)
212 waveInUnprepareHeader(u->hwi, hdr, sizeof(WAVEHDR));
213
214 if (hdr->dwBytesRecorded) {
215 memchunk.memblock = pa_memblock_new(u->core->mempool, hdr->dwBytesRecorded);
216 pa_assert(memchunk.memblock);
217
218 p = pa_memblock_acquire(memchunk.memblock);
219 memcpy((char*) p, hdr->lpData, hdr->dwBytesRecorded);
220 pa_memblock_release(memchunk.memblock);
221
222 memchunk.length = hdr->dwBytesRecorded;
223 memchunk.index = 0;
224
225 pa_source_post(u->source, &memchunk);
226 pa_memblock_unref(memchunk.memblock);
227 }
228
229 res = waveInPrepareHeader(u->hwi, hdr, sizeof(WAVEHDR));
230 if (res != MMSYSERR_NOERROR)
231 pa_log_error("Unable to prepare waveIn block: %d", res);
232
233 res = waveInAddBuffer(u->hwi, hdr, sizeof(WAVEHDR));
234 if (res != MMSYSERR_NOERROR)
235 pa_log_error("Unable to add waveIn block: %d", res);
236
237 free_frags--;
238 u->cur_ihdr++;
239 u->cur_ihdr %= u->fragments;
240 }
241 }
242
243 static void thread_func(void *userdata) {
244 struct userdata *u = userdata;
245
246 pa_assert(u);
247 pa_assert(u->sink || u->source);
248
249 pa_log_debug("Thread starting up");
250
251 if (u->core->realtime_scheduling)
252 pa_make_realtime(u->core->realtime_priority);
253
254 pa_thread_mq_install(&u->thread_mq);
255
256 for (;;) {
257 int ret;
258
259 if (PA_SINK_IS_OPENED(u->sink->thread_info.state) ||
260 PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {
261
262 if (u->sink->thread_info.rewind_requested)
263 pa_sink_process_rewind(u->sink, 0);
264
265 if (PA_SINK_IS_OPENED(u->sink->thread_info.state))
266 do_write(u);
267 if (PA_SOURCE_IS_OPENED(u->source->thread_info.state))
268 do_read(u);
269
270 pa_rtpoll_set_timer_relative(u->rtpoll, u->poll_timeout);
271 } else
272 pa_rtpoll_set_timer_disabled(u->rtpoll);
273
274 /* Hmm, nothing to do. Let's sleep */
275 if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
276 goto fail;
277
278 if (ret == 0)
279 goto finish;
280 }
281
282 fail:
283 /* If this was no regular exit from the loop we have to continue
284 * processing messages until we received PA_MESSAGE_SHUTDOWN */
285 pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
286 pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
287
288 finish:
289 pa_log_debug("Thread shutting down");
290 }
291
292 static void CALLBACK chunk_done_cb(HWAVEOUT hwo, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) {
293 struct userdata *u = (struct userdata *)inst;
294
295 if (msg != WOM_DONE)
296 return;
297
298 EnterCriticalSection(&u->crit);
299 u->free_ofrags++;
300 pa_assert(u->free_ofrags <= u->fragments);
301 LeaveCriticalSection(&u->crit);
302 }
303
304 static void CALLBACK chunk_ready_cb(HWAVEIN hwi, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) {
305 struct userdata *u = (struct userdata *)inst;
306
307 if (msg != WIM_DATA)
308 return;
309
310 EnterCriticalSection(&u->crit);
311 u->free_ifrags++;
312 pa_assert(u->free_ifrags <= u->fragments);
313 LeaveCriticalSection(&u->crit);
314 }
315
316 static pa_usec_t sink_get_latency(struct userdata *u) {
317 uint32_t free_frags;
318 MMTIME mmt;
319 pa_assert(u);
320 pa_assert(u->sink);
321
322 memset(&mmt, 0, sizeof(mmt));
323 mmt.wType = TIME_BYTES;
324 if (waveOutGetPosition(u->hwo, &mmt, sizeof(mmt)) == MMSYSERR_NOERROR)
325 return pa_bytes_to_usec(u->written_bytes - mmt.u.cb, &u->sink->sample_spec);
326 else {
327 EnterCriticalSection(&u->crit);
328 free_frags = u->free_ofrags;
329 LeaveCriticalSection(&u->crit);
330
331 return pa_bytes_to_usec((u->fragments - free_frags) * u->fragment_size, &u->sink->sample_spec);
332 }
333 }
334
335 static pa_usec_t source_get_latency(struct userdata *u) {
336 pa_usec_t r = 0;
337 uint32_t free_frags;
338 pa_assert(u);
339 pa_assert(u->source);
340
341 EnterCriticalSection(&u->crit);
342 free_frags = u->free_ifrags;
343 LeaveCriticalSection(&u->crit);
344
345 r += pa_bytes_to_usec((free_frags + 1) * u->fragment_size, &u->source->sample_spec);
346
347 return r;
348 }
349
350 static int process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
351 struct userdata *u;
352
353 if (pa_sink_isinstance(o)) {
354 u = PA_SINK(o)->userdata;
355
356 switch (code) {
357
358 case PA_SINK_MESSAGE_GET_LATENCY: {
359 pa_usec_t r = 0;
360 if (u->hwo)
361 r = sink_get_latency(u);
362 *((pa_usec_t*) data) = r;
363 return 0;
364 }
365
366 }
367
368 return pa_sink_process_msg(o, code, data, offset, chunk);
369 }
370
371 if (pa_source_isinstance(o)) {
372 u = PA_SOURCE(o)->userdata;
373
374 switch (code) {
375
376 case PA_SOURCE_MESSAGE_GET_LATENCY: {
377 pa_usec_t r = 0;
378 if (u->hwi)
379 r = source_get_latency(u);
380 *((pa_usec_t*) data) = r;
381 return 0;
382 }
383
384 }
385
386 return pa_source_process_msg(o, code, data, offset, chunk);
387 }
388
389 return -1;
390 }
391
392 static void sink_get_volume_cb(pa_sink *s) {
393 struct userdata *u = s->userdata;
394 DWORD vol;
395 pa_volume_t left, right;
396
397 if (waveOutGetVolume(u->hwo, &vol) != MMSYSERR_NOERROR)
398 return;
399
400 left = PA_CLAMP_VOLUME((vol & 0xFFFF) * PA_VOLUME_NORM / WAVEOUT_MAX_VOLUME);
401 right = PA_CLAMP_VOLUME(((vol >> 16) & 0xFFFF) * PA_VOLUME_NORM / WAVEOUT_MAX_VOLUME);
402
403 /* Windows supports > 2 channels, except for volume control */
404 if (s->real_volume.channels > 2)
405 pa_cvolume_set(&s->real_volume, s->real_volume.channels, (left + right)/2);
406
407 s->real_volume.values[0] = left;
408 if (s->real_volume.channels > 1)
409 s->real_volume.values[1] = right;
410 }
411
412 static void sink_set_volume_cb(pa_sink *s) {
413 struct userdata *u = s->userdata;
414 DWORD vol;
415
416 vol = s->real_volume.values[0] * WAVEOUT_MAX_VOLUME / PA_VOLUME_NORM;
417 if (s->real_volume.channels > 1)
418 vol |= (s->real_volume.values[1] * WAVEOUT_MAX_VOLUME / PA_VOLUME_NORM) << 16;
419
420 if (waveOutSetVolume(u->hwo, vol) != MMSYSERR_NOERROR)
421 return;
422 }
423
424 static int ss_to_waveformat(pa_sample_spec *ss, LPWAVEFORMATEX wf) {
425 wf->wFormatTag = WAVE_FORMAT_PCM;
426
427 if (ss->channels > 2) {
428 pa_log_error("More than two channels not supported.");
429 return -1;
430 }
431
432 wf->nChannels = ss->channels;
433
434 switch (ss->rate) {
435 case 8000:
436 case 11025:
437 case 22005:
438 case 44100:
439 break;
440 default:
441 pa_log_error("Unsupported sample rate.");
442 return -1;
443 }
444
445 wf->nSamplesPerSec = ss->rate;
446
447 if (ss->format == PA_SAMPLE_U8)
448 wf->wBitsPerSample = 8;
449 else if (ss->format == PA_SAMPLE_S16NE)
450 wf->wBitsPerSample = 16;
451 else {
452 pa_log_error("Unsupported sample format.");
453 return -1;
454 }
455
456 wf->nBlockAlign = wf->nChannels * wf->wBitsPerSample/8;
457 wf->nAvgBytesPerSec = wf->nSamplesPerSec * wf->nBlockAlign;
458
459 wf->cbSize = 0;
460
461 return 0;
462 }
463
464 int pa__get_n_used(pa_module *m) {
465 struct userdata *u;
466 pa_assert(m);
467 pa_assert(m->userdata);
468 u = (struct userdata *)m->userdata;
469
470 return (u->sink ? pa_sink_used_by(u->sink) : 0) +
471 (u->source ? pa_source_used_by(u->source) : 0);
472 }
473
474 int pa__init(pa_module *m) {
475 struct userdata *u = NULL;
476 HWAVEOUT hwo = INVALID_HANDLE_VALUE;
477 HWAVEIN hwi = INVALID_HANDLE_VALUE;
478 WAVEFORMATEX wf;
479 int nfrags, frag_size;
480 pa_bool_t record = TRUE, playback = TRUE;
481 unsigned int device;
482 pa_sample_spec ss;
483 pa_channel_map map;
484 pa_modargs *ma = NULL;
485 unsigned int i;
486
487 pa_assert(m);
488 pa_assert(m->core);
489
490 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
491 pa_log("failed to parse module arguments.");
492 goto fail;
493 }
494
495 if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
496 pa_log("record= and playback= expect boolean argument.");
497 goto fail;
498 }
499
500 if (!playback && !record) {
501 pa_log("neither playback nor record enabled for device.");
502 goto fail;
503 }
504
505 device = WAVE_MAPPER;
506 if (pa_modargs_get_value_u32(ma, "device", &device) < 0) {
507 pa_log("failed to parse device argument");
508 goto fail;
509 }
510
511 nfrags = 5;
512 frag_size = 8192;
513 if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) {
514 pa_log("failed to parse fragments arguments");
515 goto fail;
516 }
517
518 ss = m->core->default_sample_spec;
519 if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_WAVEEX) < 0) {
520 pa_log("failed to parse sample specification");
521 goto fail;
522 }
523
524 if (ss_to_waveformat(&ss, &wf) < 0)
525 goto fail;
526
527 u = pa_xmalloc(sizeof(struct userdata));
528
529 if (record) {
530 if (waveInOpen(&hwi, device, &wf, (DWORD_PTR)chunk_ready_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
531 pa_log("failed to open waveIn");
532 goto fail;
533 }
534 if (waveInStart(hwi) != MMSYSERR_NOERROR) {
535 pa_log("failed to start waveIn");
536 goto fail;
537 }
538 pa_log_debug("Opened waveIn subsystem.");
539 }
540
541 if (playback) {
542 if (waveOutOpen(&hwo, device, &wf, (DWORD_PTR)chunk_done_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
543 pa_log("failed to open waveOut");
544 goto fail;
545 }
546 pa_log_debug("Opened waveOut subsystem.");
547 }
548
549 InitializeCriticalSection(&u->crit);
550
551 if (hwi != INVALID_HANDLE_VALUE) {
552 pa_source_new_data data;
553 pa_source_new_data_init(&data);
554 data.driver = __FILE__;
555 data.module = m;
556 pa_source_new_data_set_sample_spec(&data, &ss);
557 pa_source_new_data_set_channel_map(&data, &map);
558 pa_source_new_data_set_name(&data, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME));
559 u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);
560 pa_source_new_data_done(&data);
561
562 pa_assert(u->source);
563 u->source->userdata = u;
564 pa_source_set_description(u->source, "Windows waveIn PCM");
565 u->source->parent.process_msg = process_msg;
566 } else
567 u->source = NULL;
568
569 if (hwo != INVALID_HANDLE_VALUE) {
570 pa_sink_new_data data;
571 pa_sink_new_data_init(&data);
572 data.driver = __FILE__;
573 data.module = m;
574 pa_sink_new_data_set_sample_spec(&data, &ss);
575 pa_sink_new_data_set_channel_map(&data, &map);
576 pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME));
577 u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY);
578 pa_sink_new_data_done(&data);
579
580 pa_assert(u->sink);
581 u->sink->get_volume = sink_get_volume_cb;
582 u->sink->set_volume = sink_set_volume_cb;
583 u->sink->userdata = u;
584 pa_sink_set_description(u->sink, "Windows waveOut PCM");
585 u->sink->parent.process_msg = process_msg;
586 } else
587 u->sink = NULL;
588
589 pa_assert(u->source || u->sink);
590 pa_modargs_free(ma);
591
592 u->core = m->core;
593 u->hwi = hwi;
594 u->hwo = hwo;
595
596 u->fragments = nfrags;
597 u->free_ifrags = u->fragments;
598 u->free_ofrags = u->fragments;
599 u->fragment_size = frag_size - (frag_size % pa_frame_size(&ss));
600
601 u->written_bytes = 0;
602 u->sink_underflow = 1;
603
604 u->poll_timeout = pa_bytes_to_usec(u->fragments * u->fragment_size / 10, &ss);
605
606 u->cur_ihdr = 0;
607 u->cur_ohdr = 0;
608 u->ihdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments);
609 pa_assert(u->ihdrs);
610 u->ohdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments);
611 pa_assert(u->ohdrs);
612 for (i = 0;i < u->fragments;i++) {
613 u->ihdrs[i].dwBufferLength = u->fragment_size;
614 u->ohdrs[i].dwBufferLength = u->fragment_size;
615 u->ihdrs[i].lpData = pa_xmalloc(u->fragment_size);
616 pa_assert(u->ihdrs);
617 u->ohdrs[i].lpData = pa_xmalloc(u->fragment_size);
618 pa_assert(u->ohdrs);
619 }
620
621 u->module = m;
622 m->userdata = u;
623
624 /* Read mixer settings */
625 if (u->sink)
626 sink_get_volume_cb(u->sink);
627
628 u->rtpoll = pa_rtpoll_new();
629 pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
630 if (!(u->thread = pa_thread_new("waveout-source", thread_func, u))) {
631 pa_log("Failed to create thread.");
632 goto fail;
633 }
634
635 if (u->sink) {
636 pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
637 pa_sink_set_rtpoll(u->sink, u->rtpoll);
638 pa_sink_put(u->sink);
639 }
640 if (u->source) {
641 pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
642 pa_source_set_rtpoll(u->source, u->rtpoll);
643 pa_source_put(u->source);
644 }
645
646 return 0;
647
648 fail:
649 if (ma)
650 pa_modargs_free(ma);
651
652 pa__done(m);
653
654 return -1;
655 }
656
657 void pa__done(pa_module *m) {
658 struct userdata *u;
659 unsigned int i;
660
661 pa_assert(m);
662 pa_assert(m->core);
663
664 if (!(u = m->userdata))
665 return;
666
667 if (u->sink)
668 pa_sink_unlink(u->sink);
669 if (u->source)
670 pa_source_unlink(u->source);
671
672 pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
673 if (u->thread)
674 pa_thread_free(u->thread);
675 pa_thread_mq_done(&u->thread_mq);
676
677 if (u->sink)
678 pa_sink_unref(u->sink);
679 if (u->source)
680 pa_source_unref(u->source);
681
682 if (u->rtpoll)
683 pa_rtpoll_free(u->rtpoll);
684
685 if (u->hwi != INVALID_HANDLE_VALUE) {
686 waveInReset(u->hwi);
687 waveInClose(u->hwi);
688 }
689
690 if (u->hwo != INVALID_HANDLE_VALUE) {
691 waveOutReset(u->hwo);
692 waveOutClose(u->hwo);
693 }
694
695 for (i = 0;i < u->fragments;i++) {
696 pa_xfree(u->ihdrs[i].lpData);
697 pa_xfree(u->ohdrs[i].lpData);
698 }
699
700 pa_xfree(u->ihdrs);
701 pa_xfree(u->ohdrs);
702
703 DeleteCriticalSection(&u->crit);
704
705 pa_xfree(u);
706 }