Home | History | Annotate | Download | only in audio
      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
     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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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*
    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
    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