blob: 16b86358bc3b8a9cc27246a825b7d9a07a678720 [file] [log] [blame]
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001/*
2 * QEMU "simple" Windows audio driver
3 *
4 * Copyright (c) 2007 The Android Open Source Project
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24#define WIN32_LEAN_AND_MEAN
25#include <windows.h>
26#include <mmsystem.h>
27
28#define AUDIO_CAP "winaudio"
29#include "audio_int.h"
30
31/* define DEBUG to 1 to dump audio debugging info at runtime to stderr */
32#define DEBUG 0
33
34#if 1
35# define D_ACTIVE 1
36#else
37# define D_ACTIVE DEBUG
38#endif
39
40#if DEBUG
41# define D(...) do{ if (D_ACTIVE) printf(__VA_ARGS__); } while(0)
42#else
43# define D(...) ((void)0)
44#endif
45
46static struct {
47 int nb_samples;
48} conf = {
49 1024
50};
51
52#if DEBUG
53int64_t start_time;
54int64_t last_time;
55#endif
56
57#define NUM_OUT_BUFFERS 8 /* must be at least 2 */
58
59/** COMMON UTILITIES
60 **/
61
62#if DEBUG
63static void
64dump_mmerror( const char* func, MMRESULT error )
65{
66 const char* reason = NULL;
67
68 fprintf(stderr, "%s returned error: ", func);
69 switch (error) {
70 case MMSYSERR_ALLOCATED: reason="specified resource is already allocated"; break;
71 case MMSYSERR_BADDEVICEID: reason="bad device id"; break;
72 case MMSYSERR_NODRIVER: reason="no driver is present"; break;
73 case MMSYSERR_NOMEM: reason="unable to allocate or lock memory"; break;
74 case WAVERR_BADFORMAT: reason="unsupported waveform-audio format"; break;
75 case WAVERR_SYNC: reason="device is synchronous"; break;
76 default:
77 fprintf(stderr, "unknown(%d)\n", error);
78 }
79 if (reason)
80 fprintf(stderr, "%s\n", reason);
81}
82#else
83# define dump_mmerror(func,error) ((void)0)
84#endif
85
86
87/** AUDIO OUT
88 **/
89
90typedef struct WinAudioOut {
91 HWVoiceOut hw;
92 HWAVEOUT waveout;
93 int silence;
94 CRITICAL_SECTION lock;
95 unsigned char* buffer_bytes;
96 WAVEHDR buffers[ NUM_OUT_BUFFERS ];
97 int write_index; /* starting first writable buffer */
98 int write_count; /* available writable buffers count */
99 int write_pos; /* position in current writable buffer */
100 int write_size; /* size in bytes of each buffer */
101} WinAudioOut;
102
103/* The Win32 callback that is called when a buffer has finished playing */
104static void CALLBACK
105winaudio_out_buffer_done (HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
106 DWORD dwParam1, DWORD dwParam2)
107{
108 WinAudioOut* s = (WinAudioOut*) dwInstance;
109
110 /* Only service "buffer done playing" messages */
111 if ( uMsg != WOM_DONE )
112 return;
113
114 /* Signal that we are done playing a buffer */
115 EnterCriticalSection( &s->lock );
116 if (s->write_count < NUM_OUT_BUFFERS)
117 s->write_count += 1;
118 LeaveCriticalSection( &s->lock );
119}
120
121static int
122winaudio_out_write (SWVoiceOut *sw, void *buf, int len)
123{
124 return audio_pcm_sw_write (sw, buf, len);
125}
126
127static void
128winaudio_out_fini (HWVoiceOut *hw)
129{
130 WinAudioOut* s = (WinAudioOut*) hw;
131 int i;
132
133 if (s->waveout) {
134 waveOutReset(s->waveout);
135 s->waveout = 0;
136 }
137
138 for ( i=0; i<NUM_OUT_BUFFERS; ++i ) {
139 if ( s->buffers[i].dwUser != 0xFFFF ) {
140 waveOutUnprepareHeader(
141 s->waveout, &s->buffers[i], sizeof(s->buffers[i]) );
142 s->buffers[i].dwUser = 0xFFFF;
143 }
144 }
145
146 if (s->buffer_bytes != NULL) {
David 'Digit' Turneraa8236d2014-01-10 17:02:29 +0100147 g_free(s->buffer_bytes);
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800148 s->buffer_bytes = NULL;
149 }
150
151 if (s->waveout) {
152 waveOutClose(s->waveout);
153 s->waveout = NULL;
154 }
155}
156
157
158static int
David 'Digit' Turner5d8f37a2009-09-14 14:32:27 -0700159winaudio_out_init (HWVoiceOut *hw, struct audsettings *as)
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800160{
161 WinAudioOut* s = (WinAudioOut*) hw;
162 MMRESULT result;
163 WAVEFORMATEX format;
164 int shift, i, samples_size;
165
166 s->waveout = NULL;
167 InitializeCriticalSection( &s->lock );
168 for (i = 0; i < NUM_OUT_BUFFERS; i++) {
169 s->buffers[i].dwUser = 0xFFFF;
170 }
171 s->buffer_bytes = NULL;
172
173 /* compute desired wave output format */
174 format.wFormatTag = WAVE_FORMAT_PCM;
175 format.nChannels = as->nchannels;
176 format.nSamplesPerSec = as->freq;
177 format.nAvgBytesPerSec = as->freq*as->nchannels;
178
179 s->silence = 0;
180
181 switch (as->fmt) {
182 case AUD_FMT_S8: shift = 0; break;
183 case AUD_FMT_U8: shift = 0; s->silence = 0x80; break;
184 case AUD_FMT_S16: shift = 1; break;
185 case AUD_FMT_U16: shift = 1; s->silence = 0x8000; break;
186 default:
187 fprintf(stderr, "qemu: winaudio: Bad output audio format: %d\n",
188 as->fmt);
189 return -1;
190 }
191
192 format.nAvgBytesPerSec = (format.nSamplesPerSec & format.nChannels) << shift;
193 format.nBlockAlign = format.nChannels << shift;
194 format.wBitsPerSample = 8 << shift;
195 format.cbSize = 0;
196
197 /* open the wave out device */
198 result = waveOutOpen( &s->waveout, WAVE_MAPPER, &format,
199 (DWORD_PTR)winaudio_out_buffer_done, (DWORD_PTR) hw,
200 CALLBACK_FUNCTION);
201 if ( result != MMSYSERR_NOERROR ) {
202 dump_mmerror( "qemu: winaudio: waveOutOpen()", result);
203 return -1;
204 }
205
206 samples_size = format.nBlockAlign * conf.nb_samples;
David 'Digit' Turneraa8236d2014-01-10 17:02:29 +0100207 s->buffer_bytes = g_malloc( NUM_OUT_BUFFERS * samples_size );
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800208 if (s->buffer_bytes == NULL) {
209 waveOutClose( s->waveout );
210 s->waveout = NULL;
211 fprintf(stderr, "not enough memory for Windows audio buffers\n");
212 return -1;
213 }
214
215 for (i = 0; i < NUM_OUT_BUFFERS; i++) {
216 memset( &s->buffers[i], 0, sizeof(s->buffers[i]) );
217 s->buffers[i].lpData = (LPSTR)(s->buffer_bytes + i*samples_size);
218 s->buffers[i].dwBufferLength = samples_size;
219 s->buffers[i].dwFlags = WHDR_DONE;
220
221 result = waveOutPrepareHeader( s->waveout, &s->buffers[i],
222 sizeof(s->buffers[i]) );
223 if ( result != MMSYSERR_NOERROR ) {
224 dump_mmerror("waveOutPrepareHeader()", result);
225 return -1;
226 }
227 }
228
229#if DEBUG
230 /* Check the sound device we retrieved */
231 {
232 WAVEOUTCAPS caps;
233
234 result = waveOutGetDevCaps((UINT) s->waveout, &caps, sizeof(caps));
235 if ( result != MMSYSERR_NOERROR ) {
236 dump_mmerror("waveOutGetDevCaps()", result);
237 } else
238 printf("Audio out device: %s\n", caps.szPname);
239 }
240#endif
241
242 audio_pcm_init_info (&hw->info, as);
243 hw->samples = conf.nb_samples*2;
244
245 s->write_index = 0;
246 s->write_count = NUM_OUT_BUFFERS;
247 s->write_pos = 0;
248 s->write_size = samples_size;
249 return 0;
250}
251
252
253static int
David 'Digit' Turner5d0e37b2011-01-02 12:58:51 +0100254winaudio_out_run (HWVoiceOut *hw, int live)
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800255{
256 WinAudioOut* s = (WinAudioOut*) hw;
257 int played = 0;
258 int has_buffer;
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800259
260 if (!live) {
261 return 0;
262 }
263
264 EnterCriticalSection( &s->lock );
265 has_buffer = (s->write_count > 0);
266 LeaveCriticalSection( &s->lock );
267
268 if (has_buffer) {
269 while (live > 0) {
270 WAVEHDR* wav_buffer = s->buffers + s->write_index;
271 int wav_bytes = (s->write_size - s->write_pos);
272 int wav_samples = audio_MIN(wav_bytes >> hw->info.shift, live);
273 int hw_samples = audio_MIN(hw->samples - hw->rpos, live);
David 'Digit' Turner5d8f37a2009-09-14 14:32:27 -0700274 struct st_sample* src = hw->mix_buf + hw->rpos;
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800275 uint8_t* dst = (uint8_t*)wav_buffer->lpData + s->write_pos;
276
277 if (wav_samples > hw_samples) {
278 wav_samples = hw_samples;
279 }
280
281 wav_bytes = wav_samples << hw->info.shift;
282
283 //D("run_out: buffer:%d pos:%d size:%d wsamples:%d wbytes:%d live:%d rpos:%d hwsamples:%d\n", s->write_index,
284 // s->write_pos, s->write_size, wav_samples, wav_bytes, live, hw->rpos, hw->samples);
285 hw->clip (dst, src, wav_samples);
286 hw->rpos += wav_samples;
287 if (hw->rpos >= hw->samples)
288 hw->rpos -= hw->samples;
289
290 live -= wav_samples;
291 played += wav_samples;
292 s->write_pos += wav_bytes;
293 if (s->write_pos == s->write_size) {
294#if xxDEBUG
David 'Digit' Turnerdcda9492014-02-16 15:13:55 +0100295 int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - start_time;
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800296 int64_t diff = now - last_time;
297
298 D("run_out: (%7.3f:%7d):waveOutWrite buffer:%d\n",
299 now/1e9, (now-last_time)/1e9, s->write_index);
300 last_time = now;
301#endif
302 waveOutWrite( s->waveout, wav_buffer, sizeof(*wav_buffer) );
303 s->write_pos = 0;
304 s->write_index += 1;
305 if (s->write_index == NUM_OUT_BUFFERS)
306 s->write_index = 0;
307
308 EnterCriticalSection( &s->lock );
309 if (--s->write_count == 0) {
310 live = 0;
311 }
312 LeaveCriticalSection( &s->lock );
313 }
314 }
315
316 }
317 return played;
318}
319
320static int
321winaudio_out_ctl (HWVoiceOut *hw, int cmd, ...)
322{
323 WinAudioOut* s = (WinAudioOut*) hw;
324
325 switch (cmd) {
326 case VOICE_ENABLE:
327 waveOutRestart( s->waveout );
328 break;
329
330 case VOICE_DISABLE:
331 waveOutPause( s->waveout );
332 break;
333 }
334 return 0;
335}
336
337/** AUDIO IN
338 **/
339
340#define NUM_IN_BUFFERS 2
341
342typedef struct WinAudioIn {
343 HWVoiceIn hw;
344 HWAVEIN wavein;
345 CRITICAL_SECTION lock;
346 unsigned char* buffer_bytes;
347 WAVEHDR buffers[ NUM_IN_BUFFERS ];
348 int read_index;
349 int read_count;
350 int read_pos;
351 int read_size;
352} WinAudioIn;
353
354/* The Win32 callback that is called when a buffer has finished playing */
355static void CALLBACK
356winaudio_in_buffer_done (HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance,
357 DWORD dwParam1, DWORD dwParam2)
358{
359 WinAudioIn* s = (WinAudioIn*) dwInstance;
360
361 /* Only service "buffer done playing" messages */
362 if ( uMsg != WIM_DATA )
363 return;
364
365 /* Signal that we are done playing a buffer */
366 EnterCriticalSection( &s->lock );
367 if (s->read_count < NUM_IN_BUFFERS)
368 s->read_count += 1;
369 //D(".%c",s->read_count + '0'); fflush(stdout);
370 LeaveCriticalSection( &s->lock );
371}
372
373static void
374winaudio_in_fini (HWVoiceIn *hw)
375{
376 WinAudioIn* s = (WinAudioIn*) hw;
377 int i;
378
379 if (s->wavein) {
380 waveInReset(s->wavein);
381 s->wavein = 0;
382 }
383
Vladimir Chtchetkineaef4c4e2012-01-05 13:35:08 -0800384 for ( i=0; i<NUM_IN_BUFFERS; ++i ) {
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800385 if ( s->buffers[i].dwUser != 0xFFFF ) {
386 waveInUnprepareHeader(
387 s->wavein, &s->buffers[i], sizeof(s->buffers[i]) );
388 s->buffers[i].dwUser = 0xFFFF;
389 }
390 }
391
392 if (s->buffer_bytes != NULL) {
David 'Digit' Turneraa8236d2014-01-10 17:02:29 +0100393 g_free(s->buffer_bytes);
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800394 s->buffer_bytes = NULL;
395 }
396
397 if (s->wavein) {
398 waveInClose(s->wavein);
399 s->wavein = NULL;
400 }
401}
402
403
404static int
David 'Digit' Turner5d8f37a2009-09-14 14:32:27 -0700405winaudio_in_init (HWVoiceIn *hw, struct audsettings *as)
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800406{
407 WinAudioIn* s = (WinAudioIn*) hw;
408 MMRESULT result;
409 WAVEFORMATEX format;
410 int shift, i, samples_size;
411
412 s->wavein = NULL;
413 InitializeCriticalSection( &s->lock );
Vladimir Chtchetkineaef4c4e2012-01-05 13:35:08 -0800414 for (i = 0; i < NUM_IN_BUFFERS; i++) {
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800415 s->buffers[i].dwUser = 0xFFFF;
416 }
417 s->buffer_bytes = NULL;
418
419 /* compute desired wave input format */
420 format.wFormatTag = WAVE_FORMAT_PCM;
421 format.nChannels = as->nchannels;
422 format.nSamplesPerSec = as->freq;
423 format.nAvgBytesPerSec = as->freq*as->nchannels;
424
425 switch (as->fmt) {
426 case AUD_FMT_S8: shift = 0; break;
427 case AUD_FMT_U8: shift = 0; break;
428 case AUD_FMT_S16: shift = 1; break;
429 case AUD_FMT_U16: shift = 1; break;
430 default:
431 fprintf(stderr, "qemu: winaudio: Bad input audio format: %d\n",
432 as->fmt);
433 return -1;
434 }
435
436 format.nAvgBytesPerSec = (format.nSamplesPerSec * format.nChannels) << shift;
437 format.nBlockAlign = format.nChannels << shift;
438 format.wBitsPerSample = 8 << shift;
439 format.cbSize = 0;
440
441 /* open the wave in device */
442 result = waveInOpen( &s->wavein, WAVE_MAPPER, &format,
443 (DWORD_PTR)winaudio_in_buffer_done, (DWORD_PTR) hw,
444 CALLBACK_FUNCTION);
445 if ( result != MMSYSERR_NOERROR ) {
446 dump_mmerror( "qemu: winaudio: waveInOpen()", result);
447 return -1;
448 }
449
450 samples_size = format.nBlockAlign * conf.nb_samples;
David 'Digit' Turneraa8236d2014-01-10 17:02:29 +0100451 s->buffer_bytes = g_malloc( NUM_IN_BUFFERS * samples_size );
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800452 if (s->buffer_bytes == NULL) {
453 waveInClose( s->wavein );
454 s->wavein = NULL;
455 fprintf(stderr, "not enough memory for Windows audio buffers\n");
456 return -1;
457 }
458
459 for (i = 0; i < NUM_IN_BUFFERS; i++) {
460 memset( &s->buffers[i], 0, sizeof(s->buffers[i]) );
461 s->buffers[i].lpData = (LPSTR)(s->buffer_bytes + i*samples_size);
462 s->buffers[i].dwBufferLength = samples_size;
463 s->buffers[i].dwFlags = WHDR_DONE;
464
465 result = waveInPrepareHeader( s->wavein, &s->buffers[i],
466 sizeof(s->buffers[i]) );
467 if ( result != MMSYSERR_NOERROR ) {
468 dump_mmerror("waveInPrepareHeader()", result);
469 return -1;
470 }
471
472 result = waveInAddBuffer( s->wavein, &s->buffers[i],
473 sizeof(s->buffers[i]) );
474 if ( result != MMSYSERR_NOERROR ) {
475 dump_mmerror("waveInAddBuffer()", result);
476 return -1;
477 }
478 }
479
480#if DEBUG
481 /* Check the sound device we retrieved */
482 {
483 WAVEINCAPS caps;
484
485 result = waveInGetDevCaps((UINT) s->wavein, &caps, sizeof(caps));
486 if ( result != MMSYSERR_NOERROR ) {
487 dump_mmerror("waveInGetDevCaps()", result);
488 } else
489 printf("Audio in device: %s\n", caps.szPname);
490 }
491#endif
492
493 audio_pcm_init_info (&hw->info, as);
494 hw->samples = conf.nb_samples*2;
495
496 s->read_index = 0;
497 s->read_count = 0;
498 s->read_pos = 0;
499 s->read_size = samples_size;
500 return 0;
501}
502
503
504/* report the number of captured samples to the audio subsystem */
505static int
506winaudio_in_run (HWVoiceIn *hw)
507{
508 WinAudioIn* s = (WinAudioIn*) hw;
509 int captured = 0;
510 int has_buffer;
511 int live = hw->samples - hw->total_samples_captured;
512
513 if (!live) {
514#if 0
515 static int counter;
516 if (++counter == 100) {
517 D("0"); fflush(stdout);
518 counter = 0;
519 }
520#endif
521 return 0;
522 }
523
524 EnterCriticalSection( &s->lock );
525 has_buffer = (s->read_count > 0);
526 LeaveCriticalSection( &s->lock );
527
528 if (has_buffer > 0) {
529 while (live > 0) {
530 WAVEHDR* wav_buffer = s->buffers + s->read_index;
531 int wav_bytes = (s->read_size - s->read_pos);
532 int wav_samples = audio_MIN(wav_bytes >> hw->info.shift, live);
533 int hw_samples = audio_MIN(hw->samples - hw->wpos, live);
David 'Digit' Turner5d8f37a2009-09-14 14:32:27 -0700534 struct st_sample* dst = hw->conv_buf + hw->wpos;
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800535 uint8_t* src = (uint8_t*)wav_buffer->lpData + s->read_pos;
536
537 if (wav_samples > hw_samples) {
538 wav_samples = hw_samples;
539 }
540
541 wav_bytes = wav_samples << hw->info.shift;
542
543 D("%s: buffer:%d pos:%d size:%d wsamples:%d wbytes:%d live:%d wpos:%d hwsamples:%d\n",
544 __FUNCTION__, s->read_index, s->read_pos, s->read_size, wav_samples, wav_bytes, live,
545 hw->wpos, hw->samples);
546
547 hw->conv(dst, src, wav_samples, &nominal_volume);
548
549 hw->wpos += wav_samples;
550 if (hw->wpos >= hw->samples)
551 hw->wpos -= hw->samples;
552
553 live -= wav_samples;
554 captured += wav_samples;
555 s->read_pos += wav_bytes;
556 if (s->read_pos == s->read_size) {
557 s->read_pos = 0;
558 s->read_index += 1;
559 if (s->read_index == NUM_IN_BUFFERS)
560 s->read_index = 0;
561
562 waveInAddBuffer( s->wavein, wav_buffer, sizeof(*wav_buffer) );
563
564 EnterCriticalSection( &s->lock );
565 if (--s->read_count == 0) {
566 live = 0;
567 }
568 LeaveCriticalSection( &s->lock );
569 }
570 }
571 }
572 return captured;
573}
574
575
576static int
577winaudio_in_read (SWVoiceIn *sw, void *buf, int len)
578{
579 int ret = audio_pcm_sw_read (sw, buf, len);
580 if (ret > 0)
581 D("%s: (%d) returned %d\n", __FUNCTION__, len, ret);
582 return ret;
583}
584
585
586static int
587winaudio_in_ctl (HWVoiceIn *hw, int cmd, ...)
588{
589 WinAudioIn* s = (WinAudioIn*) hw;
590
591 switch (cmd) {
592 case VOICE_ENABLE:
593 D("%s: enable audio in\n", __FUNCTION__);
594 waveInStart( s->wavein );
595 break;
596
597 case VOICE_DISABLE:
598 D("%s: disable audio in\n", __FUNCTION__);
599 waveInStop( s->wavein );
600 break;
601 }
602 return 0;
603}
604
605/** AUDIO STATE
606 **/
607
608typedef struct WinAudioState {
609 int dummy;
610} WinAudioState;
611
612static WinAudioState g_winaudio;
613
614static void*
615winaudio_init(void)
616{
617 WinAudioState* s = &g_winaudio;
618
619#if DEBUG
David 'Digit' Turnerdcda9492014-02-16 15:13:55 +0100620 start_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800621 last_time = 0;
622#endif
623
624 return s;
625}
626
627
628static void
629winaudio_fini (void *opaque)
630{
631}
632
633static struct audio_option winaudio_options[] = {
634 {"SAMPLES", AUD_OPT_INT, &conf.nb_samples,
635 "Size of Windows audio buffer in samples", NULL, 0},
636 {NULL, 0, NULL, NULL, NULL, 0}
637};
638
639static struct audio_pcm_ops winaudio_pcm_ops = {
640 winaudio_out_init,
641 winaudio_out_fini,
642 winaudio_out_run,
643 winaudio_out_write,
644 winaudio_out_ctl,
645
646 winaudio_in_init,
647 winaudio_in_fini,
648 winaudio_in_run,
649 winaudio_in_read,
650 winaudio_in_ctl
651};
652
653struct audio_driver win_audio_driver = {
654 INIT_FIELD (name = ) "winaudio",
655 INIT_FIELD (descr = ) "Windows wave audio",
656 INIT_FIELD (options = ) winaudio_options,
657 INIT_FIELD (init = ) winaudio_init,
658 INIT_FIELD (fini = ) winaudio_fini,
659 INIT_FIELD (pcm_ops = ) &winaudio_pcm_ops,
660 INIT_FIELD (can_be_default = ) 1,
661 INIT_FIELD (max_voices_out = ) 1,
662 INIT_FIELD (max_voices_in = ) 1,
663 INIT_FIELD (voice_size_out = ) sizeof (WinAudioOut),
664 INIT_FIELD (voice_size_in = ) sizeof (WinAudioIn)
665};