2 This file is part of PulseAudio.
4 Copyright 2006 Lennart Poettering
5 Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB
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.
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.
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
30 #include <pulse/xmalloc.h>
31 #include <pulse/timeval.h>
33 #include <pulsecore/sink.h>
34 #include <pulsecore/source.h>
35 #include <pulsecore/module.h>
36 #include <pulsecore/modargs.h>
37 #include <pulsecore/sample-util.h>
38 #include <pulsecore/core-util.h>
39 #include <pulsecore/log.h>
40 #include <pulsecore/thread.h>
41 #include <pulsecore/thread-mq.h>
43 #include "module-waveout-symdef.h"
45 PA_MODULE_AUTHOR("Pierre Ossman");
46 PA_MODULE_DESCRIPTION("Windows waveOut Sink/Source");
47 PA_MODULE_VERSION(PACKAGE_VERSION
);
49 "sink_name=<name for the sink> "
50 "source_name=<name for the source> "
51 "device=<device number> "
52 "device_name=<name of the device> "
53 "record=<enable source?> "
54 "playback=<enable sink?> "
55 "format=<sample format> "
57 "channels=<number of channels> "
58 "channel_map=<channel map> "
59 "fragments=<number of fragments> "
60 "fragment_size=<fragment size>");
62 #define DEFAULT_SINK_NAME "wave_output"
63 #define DEFAULT_SOURCE_NAME "wave_input"
65 #define WAVEOUT_MAX_VOLUME 0xFFFF
71 pa_usec_t poll_timeout
;
74 pa_thread_mq thread_mq
;
77 uint32_t fragments
, fragment_size
;
79 uint32_t free_ofrags
, free_ifrags
;
84 int cur_ohdr
, cur_ihdr
;
85 WAVEHDR
*ohdrs
, *ihdrs
;
91 CRITICAL_SECTION crit
;
94 static const char* const valid_modargs
[] = {
110 static void do_write(struct userdata
*u
) {
112 pa_memchunk memchunk
;
120 if (!PA_SINK_IS_LINKED(u
->sink
->state
))
123 EnterCriticalSection(&u
->crit
);
124 free_frags
= u
->free_ofrags
;
125 LeaveCriticalSection(&u
->crit
);
127 if (!u
->sink_underflow
&& (free_frags
== u
->fragments
))
128 pa_log_debug("WaveOut underflow!");
131 hdr
= &u
->ohdrs
[u
->cur_ohdr
];
132 if (hdr
->dwFlags
& WHDR_PREPARED
)
133 waveOutUnprepareHeader(u
->hwo
, hdr
, sizeof(WAVEHDR
));
135 hdr
->dwBufferLength
= 0;
136 while (hdr
->dwBufferLength
< u
->fragment_size
) {
139 len
= u
->fragment_size
- hdr
->dwBufferLength
;
141 pa_sink_render(u
->sink
, len
, &memchunk
);
143 pa_assert(memchunk
.memblock
);
144 pa_assert(memchunk
.length
);
146 if (memchunk
.length
< len
)
147 len
= memchunk
.length
;
149 p
= pa_memblock_acquire(memchunk
.memblock
);
150 memcpy(hdr
->lpData
+ hdr
->dwBufferLength
, (char*) p
+ memchunk
.index
, len
);
151 pa_memblock_release(memchunk
.memblock
);
153 hdr
->dwBufferLength
+= len
;
155 pa_memblock_unref(memchunk
.memblock
);
156 memchunk
.memblock
= NULL
;
159 /* Underflow detection */
160 if (hdr
->dwBufferLength
== 0) {
161 u
->sink_underflow
= 1;
164 u
->sink_underflow
= 0;
166 res
= waveOutPrepareHeader(u
->hwo
, hdr
, sizeof(WAVEHDR
));
167 if (res
!= MMSYSERR_NOERROR
)
168 pa_log_error("Unable to prepare waveOut block: %d", res
);
170 res
= waveOutWrite(u
->hwo
, hdr
, sizeof(WAVEHDR
));
171 if (res
!= MMSYSERR_NOERROR
)
172 pa_log_error("Unable to write waveOut block: %d", res
);
174 u
->written_bytes
+= hdr
->dwBufferLength
;
176 EnterCriticalSection(&u
->crit
);
178 LeaveCriticalSection(&u
->crit
);
182 u
->cur_ohdr
%= u
->fragments
;
186 static void do_read(struct userdata
*u
) {
188 pa_memchunk memchunk
;
196 if (!PA_SOURCE_IS_LINKED(u
->source
->state
))
199 EnterCriticalSection(&u
->crit
);
200 free_frags
= u
->free_ifrags
;
202 LeaveCriticalSection(&u
->crit
);
204 if (free_frags
== u
->fragments
)
205 pa_log_debug("WaveIn overflow!");
208 hdr
= &u
->ihdrs
[u
->cur_ihdr
];
209 if (hdr
->dwFlags
& WHDR_PREPARED
)
210 waveInUnprepareHeader(u
->hwi
, hdr
, sizeof(WAVEHDR
));
212 if (hdr
->dwBytesRecorded
) {
213 memchunk
.memblock
= pa_memblock_new(u
->core
->mempool
, hdr
->dwBytesRecorded
);
214 pa_assert(memchunk
.memblock
);
216 p
= pa_memblock_acquire(memchunk
.memblock
);
217 memcpy((char*) p
, hdr
->lpData
, hdr
->dwBytesRecorded
);
218 pa_memblock_release(memchunk
.memblock
);
220 memchunk
.length
= hdr
->dwBytesRecorded
;
223 pa_source_post(u
->source
, &memchunk
);
224 pa_memblock_unref(memchunk
.memblock
);
227 res
= waveInPrepareHeader(u
->hwi
, hdr
, sizeof(WAVEHDR
));
228 if (res
!= MMSYSERR_NOERROR
)
229 pa_log_error("Unable to prepare waveIn block: %d", res
);
231 res
= waveInAddBuffer(u
->hwi
, hdr
, sizeof(WAVEHDR
));
232 if (res
!= MMSYSERR_NOERROR
)
233 pa_log_error("Unable to add waveIn block: %d", res
);
237 u
->cur_ihdr
%= u
->fragments
;
241 static void thread_func(void *userdata
) {
242 struct userdata
*u
= userdata
;
245 pa_assert(u
->sink
|| u
->source
);
247 pa_log_debug("Thread starting up");
249 if (u
->core
->realtime_scheduling
)
250 pa_make_realtime(u
->core
->realtime_priority
);
252 pa_thread_mq_install(&u
->thread_mq
);
256 bool need_timer
= false;
259 if (PA_UNLIKELY(u
->sink
->thread_info
.rewind_requested
))
260 pa_sink_process_rewind(u
->sink
, 0);
262 if (PA_SINK_IS_OPENED(u
->sink
->thread_info
.state
)) {
268 if (u
->source
&& PA_SOURCE_IS_OPENED(u
->source
->thread_info
.state
)) {
274 pa_rtpoll_set_timer_relative(u
->rtpoll
, u
->poll_timeout
);
276 pa_rtpoll_set_timer_disabled(u
->rtpoll
);
278 /* Hmm, nothing to do. Let's sleep */
279 if ((ret
= pa_rtpoll_run(u
->rtpoll
, true)) < 0)
287 /* If this was no regular exit from the loop we have to continue
288 * processing messages until we received PA_MESSAGE_SHUTDOWN */
289 pa_asyncmsgq_post(u
->thread_mq
.outq
, PA_MSGOBJECT(u
->core
), PA_CORE_MESSAGE_UNLOAD_MODULE
, u
->module
, 0, NULL
, NULL
);
290 pa_asyncmsgq_wait_for(u
->thread_mq
.inq
, PA_MESSAGE_SHUTDOWN
);
293 pa_log_debug("Thread shutting down");
296 static void CALLBACK
chunk_done_cb(HWAVEOUT hwo
, UINT msg
, DWORD_PTR inst
, DWORD param1
, DWORD param2
) {
297 struct userdata
*u
= (struct userdata
*) inst
;
300 pa_log_debug("WaveOut subsystem opened.");
301 if (msg
== WOM_CLOSE
)
302 pa_log_debug("WaveOut subsystem closed.");
306 EnterCriticalSection(&u
->crit
);
308 pa_assert(u
->free_ofrags
<= u
->fragments
);
309 LeaveCriticalSection(&u
->crit
);
312 static void CALLBACK
chunk_ready_cb(HWAVEIN hwi
, UINT msg
, DWORD_PTR inst
, DWORD param1
, DWORD param2
) {
313 struct userdata
*u
= (struct userdata
*) inst
;
316 pa_log_debug("WaveIn subsystem opened.");
317 if (msg
== WIM_CLOSE
)
318 pa_log_debug("WaveIn subsystem closed.");
322 EnterCriticalSection(&u
->crit
);
324 pa_assert(u
->free_ifrags
<= u
->fragments
);
325 LeaveCriticalSection(&u
->crit
);
328 static pa_usec_t
sink_get_latency(struct userdata
*u
) {
334 memset(&mmt
, 0, sizeof(mmt
));
335 mmt
.wType
= TIME_BYTES
;
336 if (waveOutGetPosition(u
->hwo
, &mmt
, sizeof(mmt
)) == MMSYSERR_NOERROR
)
337 return pa_bytes_to_usec(u
->written_bytes
- mmt
.u
.cb
, &u
->sink
->sample_spec
);
339 EnterCriticalSection(&u
->crit
);
340 free_frags
= u
->free_ofrags
;
341 LeaveCriticalSection(&u
->crit
);
343 return pa_bytes_to_usec((u
->fragments
- free_frags
) * u
->fragment_size
, &u
->sink
->sample_spec
);
347 static pa_usec_t
source_get_latency(struct userdata
*u
) {
351 pa_assert(u
->source
);
353 EnterCriticalSection(&u
->crit
);
354 free_frags
= u
->free_ifrags
;
355 LeaveCriticalSection(&u
->crit
);
357 r
+= pa_bytes_to_usec((free_frags
+ 1) * u
->fragment_size
, &u
->source
->sample_spec
);
362 static int process_msg(pa_msgobject
*o
, int code
, void *data
, int64_t offset
, pa_memchunk
*chunk
) {
365 if (pa_sink_isinstance(o
)) {
366 u
= PA_SINK(o
)->userdata
;
370 case PA_SINK_MESSAGE_GET_LATENCY
: {
373 r
= sink_get_latency(u
);
374 *((pa_usec_t
*) data
) = r
;
380 return pa_sink_process_msg(o
, code
, data
, offset
, chunk
);
383 if (pa_source_isinstance(o
)) {
384 u
= PA_SOURCE(o
)->userdata
;
388 case PA_SOURCE_MESSAGE_GET_LATENCY
: {
391 r
= source_get_latency(u
);
392 *((pa_usec_t
*) data
) = r
;
398 return pa_source_process_msg(o
, code
, data
, offset
, chunk
);
404 static void sink_get_volume_cb(pa_sink
*s
) {
405 struct userdata
*u
= s
->userdata
;
408 pa_volume_t left
, right
;
410 if (waveOutGetDevCaps(u
->hwo
, &caps
, sizeof(caps
)) != MMSYSERR_NOERROR
)
412 if (!(caps
.dwSupport
& WAVECAPS_VOLUME
))
415 if (waveOutGetVolume(u
->hwo
, &vol
) != MMSYSERR_NOERROR
)
418 left
= PA_CLAMP_VOLUME((vol
& 0xFFFF) * PA_VOLUME_NORM
/ WAVEOUT_MAX_VOLUME
);
419 if (caps
.dwSupport
& WAVECAPS_LRVOLUME
)
420 right
= PA_CLAMP_VOLUME(((vol
>> 16) & 0xFFFF) * PA_VOLUME_NORM
/ WAVEOUT_MAX_VOLUME
);
424 /* Windows supports > 2 channels, except for volume control */
425 if (s
->real_volume
.channels
> 2)
426 pa_cvolume_set(&s
->real_volume
, s
->real_volume
.channels
, (left
+ right
)/2);
428 s
->real_volume
.values
[0] = left
;
429 if (s
->real_volume
.channels
> 1)
430 s
->real_volume
.values
[1] = right
;
433 static void sink_set_volume_cb(pa_sink
*s
) {
434 struct userdata
*u
= s
->userdata
;
438 if (waveOutGetDevCaps(u
->hwo
, &caps
, sizeof(caps
)) != MMSYSERR_NOERROR
)
440 if (!(caps
.dwSupport
& WAVECAPS_VOLUME
))
443 if (s
->real_volume
.channels
== 2 && caps
.dwSupport
& WAVECAPS_LRVOLUME
) {
444 vol
= (s
->real_volume
.values
[0] * WAVEOUT_MAX_VOLUME
/ PA_VOLUME_NORM
)
445 | (s
->real_volume
.values
[1] * WAVEOUT_MAX_VOLUME
/ PA_VOLUME_NORM
) << 16;
447 vol
= (pa_cvolume_avg(&(s
->real_volume
)) * WAVEOUT_MAX_VOLUME
/ PA_VOLUME_NORM
)
448 | (pa_cvolume_avg(&(s
->real_volume
)) * WAVEOUT_MAX_VOLUME
/ PA_VOLUME_NORM
) << 16;
451 if (waveOutSetVolume(u
->hwo
, vol
) != MMSYSERR_NOERROR
)
455 static int ss_to_waveformat(pa_sample_spec
*ss
, LPWAVEFORMATEX wf
) {
456 wf
->wFormatTag
= WAVE_FORMAT_PCM
;
458 if (ss
->channels
> 2) {
459 pa_log_error("More than two channels not supported.");
463 wf
->nChannels
= ss
->channels
;
465 wf
->nSamplesPerSec
= ss
->rate
;
467 if (ss
->format
== PA_SAMPLE_U8
)
468 wf
->wBitsPerSample
= 8;
469 else if (ss
->format
== PA_SAMPLE_S16NE
)
470 wf
->wBitsPerSample
= 16;
472 pa_log_error("Unsupported sample format, only u8 and s16 are supported.");
476 wf
->nBlockAlign
= wf
->nChannels
* wf
->wBitsPerSample
/8;
477 wf
->nAvgBytesPerSec
= wf
->nSamplesPerSec
* wf
->nBlockAlign
;
484 int pa__get_n_used(pa_module
*m
) {
487 pa_assert(m
->userdata
);
488 u
= (struct userdata
*) m
->userdata
;
490 return (u
->sink
? pa_sink_used_by(u
->sink
) : 0) +
491 (u
->source
? pa_source_used_by(u
->source
) : 0);
494 int pa__init(pa_module
*m
) {
495 struct userdata
*u
= NULL
;
496 HWAVEOUT hwo
= INVALID_HANDLE_VALUE
;
497 HWAVEIN hwi
= INVALID_HANDLE_VALUE
;
501 int nfrags
, frag_size
;
502 bool record
= true, playback
= true;
506 pa_modargs
*ma
= NULL
;
507 const char *device_name
= NULL
;
513 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
514 pa_log("failed to parse module arguments.");
518 if (pa_modargs_get_value_boolean(ma
, "record", &record
) < 0 || pa_modargs_get_value_boolean(ma
, "playback", &playback
) < 0) {
519 pa_log("record= and playback= expect boolean argument.");
523 if (!playback
&& !record
) {
524 pa_log("neither playback nor record enabled for device.");
528 /* Set the device to be opened. If set device_name is used,
529 * else device if set and lastly WAVE_MAPPER is the default */
530 device
= WAVE_MAPPER
;
531 if (pa_modargs_get_value_u32(ma
, "device", &device
) < 0) {
532 pa_log("failed to parse device argument");
535 if ((device_name
= pa_modargs_get_value(ma
, "device_name", NULL
)) != NULL
) {
536 unsigned int num_devices
= waveOutGetNumDevs();
537 for (i
= 0; i
< num_devices
; i
++) {
538 if (waveOutGetDevCaps(i
, &pwoc
, sizeof(pwoc
)) == MMSYSERR_NOERROR
)
539 if (_stricmp(device_name
, pwoc
.szPname
) == 0)
545 pa_log("device not found: %s", device_name
);
549 if (waveOutGetDevCaps(device
, &pwoc
, sizeof(pwoc
)) == MMSYSERR_NOERROR
)
550 device_name
= pwoc
.szPname
;
552 device_name
= "unknown";
556 if (pa_modargs_get_value_s32(ma
, "fragments", &nfrags
) < 0 || pa_modargs_get_value_s32(ma
, "fragment_size", &frag_size
) < 0) {
557 pa_log("failed to parse fragments arguments");
561 ss
= m
->core
->default_sample_spec
;
562 if (pa_modargs_get_sample_spec_and_channel_map(ma
, &ss
, &map
, PA_CHANNEL_MAP_WAVEEX
) < 0) {
563 pa_log("failed to parse sample specification");
567 if (ss_to_waveformat(&ss
, &wf
) < 0)
570 u
= pa_xmalloc(sizeof(struct userdata
));
573 result
= waveInOpen(&hwi
, device
, &wf
, 0, 0, WAVE_FORMAT_DIRECT
| WAVE_FORMAT_QUERY
);
574 if (result
!= MMSYSERR_NOERROR
) {
575 pa_log_warn("Sample spec not supported by WaveIn, falling back to default sample rate.");
576 ss
.rate
= wf
.nSamplesPerSec
= m
->core
->default_sample_spec
.rate
;
578 result
= waveInOpen(&hwi
, device
, &wf
, (DWORD_PTR
) chunk_ready_cb
, (DWORD_PTR
) u
, CALLBACK_FUNCTION
);
579 if (result
!= MMSYSERR_NOERROR
) {
580 char errortext
[MAXERRORLENGTH
];
581 pa_log("Failed to open WaveIn.");
582 if (waveInGetErrorText(result
, errortext
, sizeof(errortext
)) == MMSYSERR_NOERROR
)
583 pa_log("Error: %s", errortext
);
586 if (waveInStart(hwi
) != MMSYSERR_NOERROR
) {
587 pa_log("failed to start waveIn");
593 result
= waveOutOpen(&hwo
, device
, &wf
, 0, 0, WAVE_FORMAT_DIRECT
| WAVE_FORMAT_QUERY
);
594 if (result
!= MMSYSERR_NOERROR
) {
595 pa_log_warn("Sample spec not supported by WaveOut, falling back to default sample rate.");
596 ss
.rate
= wf
.nSamplesPerSec
= m
->core
->default_sample_spec
.rate
;
598 result
= waveOutOpen(&hwo
, device
, &wf
, (DWORD_PTR
) chunk_done_cb
, (DWORD_PTR
) u
, CALLBACK_FUNCTION
);
599 if (result
!= MMSYSERR_NOERROR
) {
600 char errortext
[MAXERRORLENGTH
];
601 pa_log("Failed to open WaveOut.");
602 if (waveOutGetErrorText(result
, errortext
, sizeof(errortext
)) == MMSYSERR_NOERROR
)
603 pa_log("Error: %s", errortext
);
608 InitializeCriticalSection(&u
->crit
);
610 if (hwi
!= INVALID_HANDLE_VALUE
) {
611 pa_source_new_data data
;
612 pa_source_new_data_init(&data
);
613 data
.driver
= __FILE__
;
615 pa_source_new_data_set_sample_spec(&data
, &ss
);
616 pa_source_new_data_set_channel_map(&data
, &map
);
617 pa_source_new_data_set_name(&data
, pa_modargs_get_value(ma
, "source_name", DEFAULT_SOURCE_NAME
));
618 pa_proplist_setf(data
.proplist
, PA_PROP_DEVICE_DESCRIPTION
, "WaveIn on %s", device_name
);
619 u
->source
= pa_source_new(m
->core
, &data
, PA_SOURCE_HARDWARE
|PA_SOURCE_LATENCY
);
620 pa_source_new_data_done(&data
);
622 pa_assert(u
->source
);
623 u
->source
->userdata
= u
;
624 u
->source
->parent
.process_msg
= process_msg
;
628 if (hwo
!= INVALID_HANDLE_VALUE
) {
629 pa_sink_new_data data
;
630 pa_sink_new_data_init(&data
);
631 data
.driver
= __FILE__
;
633 pa_sink_new_data_set_sample_spec(&data
, &ss
);
634 pa_sink_new_data_set_channel_map(&data
, &map
);
635 pa_sink_new_data_set_name(&data
, pa_modargs_get_value(ma
, "sink_name", DEFAULT_SINK_NAME
));
636 pa_proplist_setf(data
.proplist
, PA_PROP_DEVICE_DESCRIPTION
, "WaveOut on %s", device_name
);
637 u
->sink
= pa_sink_new(m
->core
, &data
, PA_SINK_HARDWARE
|PA_SINK_LATENCY
);
638 pa_sink_new_data_done(&data
);
641 pa_sink_set_get_volume_callback(u
->sink
, sink_get_volume_cb
);
642 pa_sink_set_set_volume_callback(u
->sink
, sink_set_volume_cb
);
643 u
->sink
->userdata
= u
;
644 u
->sink
->parent
.process_msg
= process_msg
;
648 pa_assert(u
->source
|| u
->sink
);
655 u
->fragments
= nfrags
;
656 u
->free_ifrags
= u
->fragments
;
657 u
->free_ofrags
= u
->fragments
;
658 u
->fragment_size
= frag_size
- (frag_size
% pa_frame_size(&ss
));
660 u
->written_bytes
= 0;
661 u
->sink_underflow
= 1;
663 u
->poll_timeout
= pa_bytes_to_usec(u
->fragments
* u
->fragment_size
/ 10, &ss
);
664 pa_log_debug("Poll timeout = %.1f ms", (double) u
->poll_timeout
/ PA_USEC_PER_MSEC
);
668 u
->ihdrs
= pa_xmalloc0(sizeof(WAVEHDR
) * u
->fragments
);
670 u
->ohdrs
= pa_xmalloc0(sizeof(WAVEHDR
) * u
->fragments
);
672 for (i
= 0; i
< u
->fragments
; i
++) {
673 u
->ihdrs
[i
].dwBufferLength
= u
->fragment_size
;
674 u
->ohdrs
[i
].dwBufferLength
= u
->fragment_size
;
675 u
->ihdrs
[i
].lpData
= pa_xmalloc(u
->fragment_size
);
677 u
->ohdrs
[i
].lpData
= pa_xmalloc(u
->fragment_size
);
684 /* Read mixer settings */
686 sink_get_volume_cb(u
->sink
);
688 u
->rtpoll
= pa_rtpoll_new();
689 pa_thread_mq_init(&u
->thread_mq
, m
->core
->mainloop
, u
->rtpoll
);
692 pa_sink_set_asyncmsgq(u
->sink
, u
->thread_mq
.inq
);
693 pa_sink_set_rtpoll(u
->sink
, u
->rtpoll
);
696 pa_source_set_asyncmsgq(u
->source
, u
->thread_mq
.inq
);
697 pa_source_set_rtpoll(u
->source
, u
->rtpoll
);
700 if (!(u
->thread
= pa_thread_new("waveout", thread_func
, u
))) {
701 pa_log("Failed to create thread.");
706 pa_sink_put(u
->sink
);
708 pa_source_put(u
->source
);
721 void pa__done(pa_module
*m
) {
728 if (!(u
= m
->userdata
))
732 pa_sink_unlink(u
->sink
);
734 pa_source_unlink(u
->source
);
736 pa_asyncmsgq_send(u
->thread_mq
.inq
, NULL
, PA_MESSAGE_SHUTDOWN
, NULL
, 0, NULL
);
738 pa_thread_free(u
->thread
);
739 pa_thread_mq_done(&u
->thread_mq
);
742 pa_sink_unref(u
->sink
);
744 pa_source_unref(u
->source
);
747 pa_rtpoll_free(u
->rtpoll
);
749 if (u
->hwi
!= INVALID_HANDLE_VALUE
) {
754 if (u
->hwo
!= INVALID_HANDLE_VALUE
) {
755 waveOutReset(u
->hwo
);
756 waveOutClose(u
->hwo
);
759 for (i
= 0; i
< u
->fragments
; i
++) {
760 pa_xfree(u
->ihdrs
[i
].lpData
);
761 pa_xfree(u
->ohdrs
[i
].lpData
);
767 DeleteCriticalSection(&u
->crit
);