• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
46 static struct {
47     int nb_samples;
48 } conf = {
49     1024
50 };
51 
52 #if DEBUG
53 int64_t  start_time;
54 int64_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
63 static void
dump_mmerror(const char * func,MMRESULT error)64 dump_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 
90 typedef 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 */
104 static void CALLBACK
winaudio_out_buffer_done(HWAVEOUT hwo,UINT uMsg,DWORD_PTR dwInstance,DWORD dwParam1,DWORD dwParam2)105 winaudio_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 
121 static int
winaudio_out_write(SWVoiceOut * sw,void * buf,int len)122 winaudio_out_write (SWVoiceOut *sw, void *buf, int len)
123 {
124     return audio_pcm_sw_write (sw, buf, len);
125 }
126 
127 static void
winaudio_out_fini(HWVoiceOut * hw)128 winaudio_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) {
147         qemu_free(s->buffer_bytes);
148         s->buffer_bytes = NULL;
149     }
150 
151     if (s->waveout) {
152         waveOutClose(s->waveout);
153         s->waveout = NULL;
154     }
155 }
156 
157 
158 static int
winaudio_out_init(HWVoiceOut * hw,struct audsettings * as)159 winaudio_out_init (HWVoiceOut *hw, struct audsettings *as)
160 {
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;
207     s->buffer_bytes = qemu_malloc( NUM_OUT_BUFFERS * samples_size );
208     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 
253 static int
winaudio_out_run(HWVoiceOut * hw)254 winaudio_out_run (HWVoiceOut *hw)
255 {
256     WinAudioOut*  s      = (WinAudioOut*) hw;
257     int           played = 0;
258     int           has_buffer;
259     int           live = audio_pcm_hw_get_live_out (hw);
260 
261     if (!live) {
262         return 0;
263     }
264 
265     EnterCriticalSection( &s->lock );
266     has_buffer = (s->write_count > 0);
267     LeaveCriticalSection( &s->lock );
268 
269     if (has_buffer) {
270         while (live > 0) {
271             WAVEHDR*      wav_buffer  = s->buffers + s->write_index;
272             int           wav_bytes   = (s->write_size - s->write_pos);
273             int           wav_samples = audio_MIN(wav_bytes >> hw->info.shift, live);
274             int           hw_samples  = audio_MIN(hw->samples - hw->rpos, live);
275             struct st_sample*  src         = hw->mix_buf + hw->rpos;
276             uint8_t*      dst         = (uint8_t*)wav_buffer->lpData + s->write_pos;
277 
278             if (wav_samples > hw_samples) {
279                     wav_samples = hw_samples;
280             }
281 
282             wav_bytes = wav_samples << hw->info.shift;
283 
284             //D("run_out: buffer:%d pos:%d size:%d wsamples:%d wbytes:%d live:%d rpos:%d hwsamples:%d\n", s->write_index,
285             //   s->write_pos, s->write_size, wav_samples, wav_bytes, live, hw->rpos, hw->samples);
286             hw->clip (dst, src, wav_samples);
287             hw->rpos += wav_samples;
288             if (hw->rpos >= hw->samples)
289                     hw->rpos -= hw->samples;
290 
291             live         -= wav_samples;
292             played       += wav_samples;
293             s->write_pos += wav_bytes;
294             if (s->write_pos == s->write_size) {
295 #if xxDEBUG
296                 int64_t  now  = qemu_get_clock(vm_clock) - start_time;
297                 int64_t  diff = now - last_time;
298 
299                 D("run_out: (%7.3f:%7d):waveOutWrite buffer:%d\n",
300                    now/1e9, (now-last_time)/1e9, s->write_index);
301                 last_time = now;
302 #endif
303                 waveOutWrite( s->waveout, wav_buffer, sizeof(*wav_buffer) );
304                 s->write_pos    = 0;
305                 s->write_index += 1;
306                 if (s->write_index == NUM_OUT_BUFFERS)
307                     s->write_index = 0;
308 
309                 EnterCriticalSection( &s->lock );
310                 if (--s->write_count == 0) {
311                         live = 0;
312                 }
313                 LeaveCriticalSection( &s->lock );
314             }
315         }
316 
317     }
318     return played;
319 }
320 
321 static int
winaudio_out_ctl(HWVoiceOut * hw,int cmd,...)322 winaudio_out_ctl (HWVoiceOut *hw, int cmd, ...)
323 {
324     WinAudioOut*  s = (WinAudioOut*) hw;
325 
326     switch (cmd) {
327     case VOICE_ENABLE:
328         waveOutRestart( s->waveout );
329         break;
330 
331     case VOICE_DISABLE:
332         waveOutPause( s->waveout );
333         break;
334     }
335     return 0;
336 }
337 
338 /** AUDIO IN
339  **/
340 
341 #define  NUM_IN_BUFFERS  2
342 
343 typedef struct WinAudioIn {
344     HWVoiceIn         hw;
345     HWAVEIN           wavein;
346     CRITICAL_SECTION  lock;
347     unsigned char*    buffer_bytes;
348     WAVEHDR           buffers[ NUM_IN_BUFFERS ];
349     int               read_index;
350     int               read_count;
351     int               read_pos;
352     int               read_size;
353 } WinAudioIn;
354 
355 /* The Win32 callback that is called when a buffer has finished playing */
356 static void CALLBACK
winaudio_in_buffer_done(HWAVEIN hwi,UINT uMsg,DWORD_PTR dwInstance,DWORD dwParam1,DWORD dwParam2)357 winaudio_in_buffer_done (HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance,
358                          DWORD dwParam1, DWORD dwParam2)
359 {
360     WinAudioIn*  s = (WinAudioIn*) dwInstance;
361 
362     /* Only service "buffer done playing" messages */
363     if ( uMsg != WIM_DATA )
364         return;
365 
366     /* Signal that we are done playing a buffer */
367     EnterCriticalSection( &s->lock );
368     if (s->read_count < NUM_IN_BUFFERS)
369         s->read_count += 1;
370         //D(".%c",s->read_count + '0'); fflush(stdout);
371     LeaveCriticalSection( &s->lock );
372 }
373 
374 static void
winaudio_in_fini(HWVoiceIn * hw)375 winaudio_in_fini (HWVoiceIn *hw)
376 {
377     WinAudioIn*  s = (WinAudioIn*) hw;
378     int           i;
379 
380     if (s->wavein) {
381         waveInReset(s->wavein);
382             s->wavein = 0;
383     }
384 
385     for ( i=0; i<NUM_OUT_BUFFERS; ++i ) {
386         if ( s->buffers[i].dwUser != 0xFFFF ) {
387             waveInUnprepareHeader(
388                 s->wavein, &s->buffers[i], sizeof(s->buffers[i]) );
389                 s->buffers[i].dwUser = 0xFFFF;
390         }
391     }
392 
393     if (s->buffer_bytes != NULL) {
394         qemu_free(s->buffer_bytes);
395         s->buffer_bytes = NULL;
396     }
397 
398     if (s->wavein) {
399         waveInClose(s->wavein);
400         s->wavein = NULL;
401     }
402 }
403 
404 
405 static int
winaudio_in_init(HWVoiceIn * hw,struct audsettings * as)406 winaudio_in_init (HWVoiceIn *hw, struct audsettings *as)
407 {
408     WinAudioIn*   s = (WinAudioIn*) hw;
409     MMRESULT       result;
410     WAVEFORMATEX   format;
411     int            shift, i, samples_size;
412 
413     s->wavein = NULL;
414     InitializeCriticalSection( &s->lock );
415     for (i = 0; i < NUM_OUT_BUFFERS; i++) {
416             s->buffers[i].dwUser = 0xFFFF;
417     }
418     s->buffer_bytes = NULL;
419 
420     /* compute desired wave input format */
421     format.wFormatTag      = WAVE_FORMAT_PCM;
422     format.nChannels       = as->nchannels;
423     format.nSamplesPerSec  = as->freq;
424     format.nAvgBytesPerSec = as->freq*as->nchannels;
425 
426     switch (as->fmt) {
427         case AUD_FMT_S8:   shift = 0; break;
428         case AUD_FMT_U8:   shift = 0; break;
429         case AUD_FMT_S16:  shift = 1; break;
430         case AUD_FMT_U16:  shift = 1; break;
431         default:
432             fprintf(stderr, "qemu: winaudio: Bad input audio format: %d\n",
433                     as->fmt);
434                 return -1;
435     }
436 
437     format.nAvgBytesPerSec = (format.nSamplesPerSec * format.nChannels) << shift;
438     format.nBlockAlign     = format.nChannels << shift;
439     format.wBitsPerSample  = 8 << shift;
440     format.cbSize          = 0;
441 
442     /* open the wave in device */
443     result = waveInOpen( &s->wavein, WAVE_MAPPER, &format,
444                          (DWORD_PTR)winaudio_in_buffer_done, (DWORD_PTR) hw,
445                          CALLBACK_FUNCTION);
446     if ( result != MMSYSERR_NOERROR ) {
447         dump_mmerror( "qemu: winaudio: waveInOpen()", result);
448             return -1;
449     }
450 
451     samples_size    = format.nBlockAlign * conf.nb_samples;
452     s->buffer_bytes = qemu_malloc( NUM_IN_BUFFERS * samples_size );
453     if (s->buffer_bytes == NULL) {
454             waveInClose( s->wavein );
455             s->wavein = NULL;
456             fprintf(stderr, "not enough memory for Windows audio buffers\n");
457             return -1;
458     }
459 
460     for (i = 0; i < NUM_IN_BUFFERS; i++) {
461         memset( &s->buffers[i], 0, sizeof(s->buffers[i]) );
462         s->buffers[i].lpData         = (LPSTR)(s->buffer_bytes + i*samples_size);
463         s->buffers[i].dwBufferLength = samples_size;
464         s->buffers[i].dwFlags        = WHDR_DONE;
465 
466         result = waveInPrepareHeader( s->wavein, &s->buffers[i],
467                                sizeof(s->buffers[i]) );
468         if ( result != MMSYSERR_NOERROR ) {
469                 dump_mmerror("waveInPrepareHeader()", result);
470                 return -1;
471         }
472 
473         result = waveInAddBuffer( s->wavein, &s->buffers[i],
474                               sizeof(s->buffers[i]) );
475         if ( result != MMSYSERR_NOERROR ) {
476             dump_mmerror("waveInAddBuffer()", result);
477             return -1;
478         }
479     }
480 
481 #if DEBUG
482     /* Check the sound device we retrieved */
483     {
484         WAVEINCAPS caps;
485 
486         result = waveInGetDevCaps((UINT) s->wavein, &caps, sizeof(caps));
487         if ( result != MMSYSERR_NOERROR ) {
488             dump_mmerror("waveInGetDevCaps()", result);
489         } else
490             printf("Audio in device: %s\n", caps.szPname);
491     }
492 #endif
493 
494     audio_pcm_init_info (&hw->info, as);
495     hw->samples = conf.nb_samples*2;
496 
497     s->read_index = 0;
498     s->read_count = 0;
499     s->read_pos   = 0;
500     s->read_size  = samples_size;
501     return 0;
502 }
503 
504 
505 /* report the number of captured samples to the audio subsystem */
506 static int
winaudio_in_run(HWVoiceIn * hw)507 winaudio_in_run (HWVoiceIn *hw)
508 {
509     WinAudioIn*  s        = (WinAudioIn*) hw;
510     int          captured = 0;
511     int          has_buffer;
512     int          live = hw->samples - hw->total_samples_captured;
513 
514     if (!live) {
515 #if 0
516         static int  counter;
517         if (++counter == 100) {
518             D("0"); fflush(stdout);
519             counter = 0;
520         }
521 #endif
522             return 0;
523     }
524 
525     EnterCriticalSection( &s->lock );
526     has_buffer = (s->read_count > 0);
527     LeaveCriticalSection( &s->lock );
528 
529     if (has_buffer > 0) {
530         while (live > 0) {
531             WAVEHDR*      wav_buffer  = s->buffers + s->read_index;
532             int           wav_bytes   = (s->read_size - s->read_pos);
533             int           wav_samples = audio_MIN(wav_bytes >> hw->info.shift, live);
534             int           hw_samples  = audio_MIN(hw->samples - hw->wpos, live);
535             struct st_sample*  dst    = hw->conv_buf + hw->wpos;
536             uint8_t*      src         = (uint8_t*)wav_buffer->lpData + s->read_pos;
537 
538             if (wav_samples > hw_samples) {
539                 wav_samples = hw_samples;
540             }
541 
542             wav_bytes = wav_samples << hw->info.shift;
543 
544             D("%s: buffer:%d pos:%d size:%d wsamples:%d wbytes:%d live:%d wpos:%d hwsamples:%d\n",
545                __FUNCTION__, s->read_index, s->read_pos, s->read_size, wav_samples, wav_bytes, live,
546                hw->wpos, hw->samples);
547 
548             hw->conv(dst, src, wav_samples, &nominal_volume);
549 
550             hw->wpos += wav_samples;
551             if (hw->wpos >= hw->samples)
552                 hw->wpos -= hw->samples;
553 
554             live        -= wav_samples;
555             captured    += wav_samples;
556             s->read_pos += wav_bytes;
557             if (s->read_pos == s->read_size) {
558                 s->read_pos    = 0;
559                 s->read_index += 1;
560                 if (s->read_index == NUM_IN_BUFFERS)
561                     s->read_index = 0;
562 
563                 waveInAddBuffer( s->wavein, wav_buffer, sizeof(*wav_buffer) );
564 
565                 EnterCriticalSection( &s->lock );
566                 if (--s->read_count == 0) {
567                     live = 0;
568                 }
569                 LeaveCriticalSection( &s->lock );
570             }
571         }
572     }
573     return  captured;
574 }
575 
576 
577 static int
winaudio_in_read(SWVoiceIn * sw,void * buf,int len)578 winaudio_in_read (SWVoiceIn *sw, void *buf, int len)
579 {
580     int  ret = audio_pcm_sw_read (sw, buf, len);
581     if (ret > 0)
582         D("%s: (%d) returned %d\n", __FUNCTION__, len, ret);
583     return ret;
584 }
585 
586 
587 static int
winaudio_in_ctl(HWVoiceIn * hw,int cmd,...)588 winaudio_in_ctl (HWVoiceIn *hw, int cmd, ...)
589 {
590 	WinAudioIn*  s = (WinAudioIn*) hw;
591 
592     switch (cmd) {
593     case VOICE_ENABLE:
594         D("%s: enable audio in\n", __FUNCTION__);
595         waveInStart( s->wavein );
596         break;
597 
598     case VOICE_DISABLE:
599         D("%s: disable audio in\n", __FUNCTION__);
600         waveInStop( s->wavein );
601         break;
602     }
603     return 0;
604 }
605 
606 /** AUDIO STATE
607  **/
608 
609 typedef struct WinAudioState {
610     int  dummy;
611 } WinAudioState;
612 
613 static WinAudioState  g_winaudio;
614 
615 static void*
winaudio_init(void)616 winaudio_init(void)
617 {
618     WinAudioState*  s = &g_winaudio;
619 
620 #if DEBUG
621     start_time = qemu_get_clock(vm_clock);
622     last_time  = 0;
623 #endif
624 
625     return s;
626 }
627 
628 
629 static void
winaudio_fini(void * opaque)630 winaudio_fini (void *opaque)
631 {
632 }
633 
634 static struct audio_option winaudio_options[] = {
635     {"SAMPLES", AUD_OPT_INT, &conf.nb_samples,
636      "Size of Windows audio buffer in samples", NULL, 0},
637     {NULL, 0, NULL, NULL, NULL, 0}
638 };
639 
640 static struct audio_pcm_ops winaudio_pcm_ops = {
641     winaudio_out_init,
642     winaudio_out_fini,
643     winaudio_out_run,
644     winaudio_out_write,
645     winaudio_out_ctl,
646 
647     winaudio_in_init,
648     winaudio_in_fini,
649     winaudio_in_run,
650     winaudio_in_read,
651     winaudio_in_ctl
652 };
653 
654 struct audio_driver win_audio_driver = {
655     INIT_FIELD (name           = ) "winaudio",
656     INIT_FIELD (descr          = ) "Windows wave audio",
657     INIT_FIELD (options        = ) winaudio_options,
658     INIT_FIELD (init           = ) winaudio_init,
659     INIT_FIELD (fini           = ) winaudio_fini,
660     INIT_FIELD (pcm_ops        = ) &winaudio_pcm_ops,
661     INIT_FIELD (can_be_default = ) 1,
662     INIT_FIELD (max_voices_out = ) 1,
663     INIT_FIELD (max_voices_in  = ) 1,
664     INIT_FIELD (voice_size_out = ) sizeof (WinAudioOut),
665     INIT_FIELD (voice_size_in  = ) sizeof (WinAudioIn)
666 };
667