• 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         g_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 = g_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,int live)254 winaudio_out_run (HWVoiceOut *hw, int live)
255 {
256     WinAudioOut*  s      = (WinAudioOut*) hw;
257     int           played = 0;
258     int           has_buffer;
259 
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);
274             struct st_sample*  src         = hw->mix_buf + hw->rpos;
275             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
295                 int64_t  now  = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - start_time;
296                 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 
320 static int
winaudio_out_ctl(HWVoiceOut * hw,int cmd,...)321 winaudio_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 
342 typedef 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 */
355 static void CALLBACK
winaudio_in_buffer_done(HWAVEIN hwi,UINT uMsg,DWORD_PTR dwInstance,DWORD dwParam1,DWORD dwParam2)356 winaudio_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 
373 static void
winaudio_in_fini(HWVoiceIn * hw)374 winaudio_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 
384     for ( i=0; i<NUM_IN_BUFFERS; ++i ) {
385         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) {
393         g_free(s->buffer_bytes);
394         s->buffer_bytes = NULL;
395     }
396 
397     if (s->wavein) {
398         waveInClose(s->wavein);
399         s->wavein = NULL;
400     }
401 }
402 
403 
404 static int
winaudio_in_init(HWVoiceIn * hw,struct audsettings * as)405 winaudio_in_init (HWVoiceIn *hw, struct audsettings *as)
406 {
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 );
414     for (i = 0; i < NUM_IN_BUFFERS; i++) {
415             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;
451     s->buffer_bytes = g_malloc( NUM_IN_BUFFERS * samples_size );
452     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 */
505 static int
winaudio_in_run(HWVoiceIn * hw)506 winaudio_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);
534             struct st_sample*  dst    = hw->conv_buf + hw->wpos;
535             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 
576 static int
winaudio_in_read(SWVoiceIn * sw,void * buf,int len)577 winaudio_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 
586 static int
winaudio_in_ctl(HWVoiceIn * hw,int cmd,...)587 winaudio_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 
608 typedef struct WinAudioState {
609     int  dummy;
610 } WinAudioState;
611 
612 static WinAudioState  g_winaudio;
613 
614 static void*
winaudio_init(void)615 winaudio_init(void)
616 {
617     WinAudioState*  s = &g_winaudio;
618 
619 #if DEBUG
620     start_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
621     last_time  = 0;
622 #endif
623 
624     return s;
625 }
626 
627 
628 static void
winaudio_fini(void * opaque)629 winaudio_fini (void *opaque)
630 {
631 }
632 
633 static 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 
639 static 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 
653 struct 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 };
666