audio/ossaudio.c for OpenBSD, by Todd T. Fries.
[qemu] / audio / ossaudio.c
1 /*
2  * QEMU OSS audio driver
3  *
4  * Copyright (c) 2003-2005 Vassili Karpov (malc)
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 #include <stdlib.h>
25 #include <sys/mman.h>
26 #include <sys/types.h>
27 #include <sys/ioctl.h>
28 #ifdef __OpenBSD__
29 #include <soundcard.h>
30 #else
31 #include <sys/soundcard.h>
32 #endif
33 #include "vl.h"
34
35 #define AUDIO_CAP "oss"
36 #include "audio_int.h"
37
38 typedef struct OSSVoiceOut {
39     HWVoiceOut hw;
40     void *pcm_buf;
41     int fd;
42     int nfrags;
43     int fragsize;
44     int mmapped;
45     int old_optr;
46 } OSSVoiceOut;
47
48 typedef struct OSSVoiceIn {
49     HWVoiceIn hw;
50     void *pcm_buf;
51     int fd;
52     int nfrags;
53     int fragsize;
54     int old_optr;
55 } OSSVoiceIn;
56
57 static struct {
58     int try_mmap;
59     int nfrags;
60     int fragsize;
61     const char *devpath_out;
62     const char *devpath_in;
63     int debug;
64 } conf = {
65     .try_mmap = 0,
66     .nfrags = 4,
67     .fragsize = 4096,
68     .devpath_out = "/dev/dsp",
69     .devpath_in = "/dev/dsp",
70     .debug = 0
71 };
72
73 struct oss_params {
74     int freq;
75     audfmt_e fmt;
76     int nchannels;
77     int nfrags;
78     int fragsize;
79 };
80
81 static void GCC_FMT_ATTR (2, 3) oss_logerr (int err, const char *fmt, ...)
82 {
83     va_list ap;
84
85     va_start (ap, fmt);
86     AUD_vlog (AUDIO_CAP, fmt, ap);
87     va_end (ap);
88
89     AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err));
90 }
91
92 static void GCC_FMT_ATTR (3, 4) oss_logerr2 (
93     int err,
94     const char *typ,
95     const char *fmt,
96     ...
97     )
98 {
99     va_list ap;
100
101     AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ);
102
103     va_start (ap, fmt);
104     AUD_vlog (AUDIO_CAP, fmt, ap);
105     va_end (ap);
106
107     AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err));
108 }
109
110 static void oss_anal_close (int *fdp)
111 {
112     int err = close (*fdp);
113     if (err) {
114         oss_logerr (errno, "Failed to close file(fd=%d)\n", *fdp);
115     }
116     *fdp = -1;
117 }
118
119 static int oss_write (SWVoiceOut *sw, void *buf, int len)
120 {
121     return audio_pcm_sw_write (sw, buf, len);
122 }
123
124 static int aud_to_ossfmt (audfmt_e fmt)
125 {
126     switch (fmt) {
127     case AUD_FMT_S8:
128         return AFMT_S8;
129
130     case AUD_FMT_U8:
131         return AFMT_U8;
132
133     case AUD_FMT_S16:
134         return AFMT_S16_LE;
135
136     case AUD_FMT_U16:
137         return AFMT_U16_LE;
138
139     default:
140         dolog ("Internal logic error: Bad audio format %d\n", fmt);
141 #ifdef DEBUG_AUDIO
142         abort ();
143 #endif
144         return AFMT_U8;
145     }
146 }
147
148 static int oss_to_audfmt (int ossfmt, audfmt_e *fmt, int *endianness)
149 {
150     switch (ossfmt) {
151     case AFMT_S8:
152         *endianness =0;
153         *fmt = AUD_FMT_S8;
154         break;
155
156     case AFMT_U8:
157         *endianness = 0;
158         *fmt = AUD_FMT_U8;
159         break;
160
161     case AFMT_S16_LE:
162         *endianness = 0;
163         *fmt = AUD_FMT_S16;
164         break;
165
166     case AFMT_U16_LE:
167         *endianness = 0;
168         *fmt = AUD_FMT_U16;
169         break;
170
171     case AFMT_S16_BE:
172         *endianness = 1;
173         *fmt = AUD_FMT_S16;
174         break;
175
176     case AFMT_U16_BE:
177         *endianness = 1;
178         *fmt = AUD_FMT_U16;
179         break;
180
181     default:
182         dolog ("Unrecognized audio format %d\n", ossfmt);
183         return -1;
184     }
185
186     return 0;
187 }
188
189 #if defined DEBUG_MISMATCHES || defined DEBUG
190 static void oss_dump_info (struct oss_params *req, struct oss_params *obt)
191 {
192     dolog ("parameter | requested value | obtained value\n");
193     dolog ("format    |      %10d |     %10d\n", req->fmt, obt->fmt);
194     dolog ("channels  |      %10d |     %10d\n",
195            req->nchannels, obt->nchannels);
196     dolog ("frequency |      %10d |     %10d\n", req->freq, obt->freq);
197     dolog ("nfrags    |      %10d |     %10d\n", req->nfrags, obt->nfrags);
198     dolog ("fragsize  |      %10d |     %10d\n",
199            req->fragsize, obt->fragsize);
200 }
201 #endif
202
203 static int oss_open (int in, struct oss_params *req,
204                      struct oss_params *obt, int *pfd)
205 {
206     int fd;
207     int mmmmssss;
208     audio_buf_info abinfo;
209     int fmt, freq, nchannels;
210     const char *dspname = in ? conf.devpath_in : conf.devpath_out;
211     const char *typ = in ? "ADC" : "DAC";
212
213     fd = open (dspname, (in ? O_RDONLY : O_WRONLY) | O_NONBLOCK);
214     if (-1 == fd) {
215         oss_logerr2 (errno, typ, "Failed to open `%s'\n", dspname);
216         return -1;
217     }
218
219     freq = req->freq;
220     nchannels = req->nchannels;
221     fmt = req->fmt;
222
223     if (ioctl (fd, SNDCTL_DSP_SAMPLESIZE, &fmt)) {
224         oss_logerr2 (errno, typ, "Failed to set sample size %d\n", req->fmt);
225         goto err;
226     }
227
228     if (ioctl (fd, SNDCTL_DSP_CHANNELS, &nchannels)) {
229         oss_logerr2 (errno, typ, "Failed to set number of channels %d\n",
230                      req->nchannels);
231         goto err;
232     }
233
234     if (ioctl (fd, SNDCTL_DSP_SPEED, &freq)) {
235         oss_logerr2 (errno, typ, "Failed to set frequency %d\n", req->freq);
236         goto err;
237     }
238
239     if (ioctl (fd, SNDCTL_DSP_NONBLOCK)) {
240         oss_logerr2 (errno, typ, "Failed to set non-blocking mode\n");
241         goto err;
242     }
243
244     mmmmssss = (req->nfrags << 16) | lsbindex (req->fragsize);
245     if (ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &mmmmssss)) {
246         oss_logerr2 (errno, typ, "Failed to set buffer length (%d, %d)\n",
247                      req->nfrags, req->fragsize);
248         goto err;
249     }
250
251     if (ioctl (fd, in ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &abinfo)) {
252         oss_logerr2 (errno, typ, "Failed to get buffer length\n");
253         goto err;
254     }
255
256     obt->fmt = fmt;
257     obt->nchannels = nchannels;
258     obt->freq = freq;
259     obt->nfrags = abinfo.fragstotal;
260     obt->fragsize = abinfo.fragsize;
261     *pfd = fd;
262
263 #ifdef DEBUG_MISMATCHES
264     if ((req->fmt != obt->fmt) ||
265         (req->nchannels != obt->nchannels) ||
266         (req->freq != obt->freq) ||
267         (req->fragsize != obt->fragsize) ||
268         (req->nfrags != obt->nfrags)) {
269         dolog ("Audio parameters mismatch\n");
270         oss_dump_info (req, obt);
271     }
272 #endif
273
274 #ifdef DEBUG
275     oss_dump_info (req, obt);
276 #endif
277     return 0;
278
279  err:
280     oss_anal_close (&fd);
281     return -1;
282 }
283
284 static int oss_run_out (HWVoiceOut *hw)
285 {
286     OSSVoiceOut *oss = (OSSVoiceOut *) hw;
287     int err, rpos, live, decr;
288     int samples;
289     uint8_t *dst;
290     st_sample_t *src;
291     struct audio_buf_info abinfo;
292     struct count_info cntinfo;
293     int bufsize;
294
295     live = audio_pcm_hw_get_live_out (hw);
296     if (!live) {
297         return 0;
298     }
299
300     bufsize = hw->samples << hw->info.shift;
301
302     if (oss->mmapped) {
303         int bytes;
304
305         err = ioctl (oss->fd, SNDCTL_DSP_GETOPTR, &cntinfo);
306         if (err < 0) {
307             oss_logerr (errno, "SNDCTL_DSP_GETOPTR failed\n");
308             return 0;
309         }
310
311         if (cntinfo.ptr == oss->old_optr) {
312             if (abs (hw->samples - live) < 64) {
313                 dolog ("warning: Overrun\n");
314             }
315             return 0;
316         }
317
318         if (cntinfo.ptr > oss->old_optr) {
319             bytes = cntinfo.ptr - oss->old_optr;
320         }
321         else {
322             bytes = bufsize + cntinfo.ptr - oss->old_optr;
323         }
324
325         decr = audio_MIN (bytes >> hw->info.shift, live);
326     }
327     else {
328         err = ioctl (oss->fd, SNDCTL_DSP_GETOSPACE, &abinfo);
329         if (err < 0) {
330             oss_logerr (errno, "SNDCTL_DSP_GETOPTR failed\n");
331             return 0;
332         }
333
334         if (abinfo.bytes > bufsize) {
335             if (conf.debug) {
336                 dolog ("warning: Invalid available size, size=%d bufsize=%d\n"
337                        "please report your OS/audio hw to malc@pulsesoft.com\n",
338                        abinfo.bytes, bufsize);
339             }
340             abinfo.bytes = bufsize;
341         }
342
343         if (abinfo.bytes < 0) {
344             if (conf.debug) {
345                 dolog ("warning: Invalid available size, size=%d bufsize=%d\n",
346                        abinfo.bytes, bufsize);
347             }
348             return 0;
349         }
350
351         decr = audio_MIN (abinfo.bytes >> hw->info.shift, live);
352         if (!decr) {
353             return 0;
354         }
355     }
356
357     samples = decr;
358     rpos = hw->rpos;
359     while (samples) {
360         int left_till_end_samples = hw->samples - rpos;
361         int convert_samples = audio_MIN (samples, left_till_end_samples);
362
363         src = hw->mix_buf + rpos;
364         dst = advance (oss->pcm_buf, rpos << hw->info.shift);
365
366         hw->clip (dst, src, convert_samples);
367         if (!oss->mmapped) {
368             int written;
369
370             written = write (oss->fd, dst, convert_samples << hw->info.shift);
371             /* XXX: follow errno recommendations ? */
372             if (written == -1) {
373                 oss_logerr (
374                     errno,
375                     "Failed to write %d bytes of audio data from %p\n",
376                     convert_samples << hw->info.shift,
377                     dst
378                     );
379                 continue;
380             }
381
382             if (written != convert_samples << hw->info.shift) {
383                 int wsamples = written >> hw->info.shift;
384                 int wbytes = wsamples << hw->info.shift;
385                 if (wbytes != written) {
386                     dolog ("warning: Misaligned write %d (requested %d), "
387                            "alignment %d\n",
388                            wbytes, written, hw->info.align + 1);
389                 }
390                 decr -= wsamples;
391                 rpos = (rpos + wsamples) % hw->samples;
392                 break;
393             }
394         }
395
396         rpos = (rpos + convert_samples) % hw->samples;
397         samples -= convert_samples;
398     }
399     if (oss->mmapped) {
400         oss->old_optr = cntinfo.ptr;
401     }
402
403     hw->rpos = rpos;
404     return decr;
405 }
406
407 static void oss_fini_out (HWVoiceOut *hw)
408 {
409     int err;
410     OSSVoiceOut *oss = (OSSVoiceOut *) hw;
411
412     ldebug ("oss_fini\n");
413     oss_anal_close (&oss->fd);
414
415     if (oss->pcm_buf) {
416         if (oss->mmapped) {
417             err = munmap (oss->pcm_buf, hw->samples << hw->info.shift);
418             if (err) {
419                 oss_logerr (errno, "Failed to unmap buffer %p, size %d\n",
420                             oss->pcm_buf, hw->samples << hw->info.shift);
421             }
422         }
423         else {
424             qemu_free (oss->pcm_buf);
425         }
426         oss->pcm_buf = NULL;
427     }
428 }
429
430 static int oss_init_out (HWVoiceOut *hw, audsettings_t *as)
431 {
432     OSSVoiceOut *oss = (OSSVoiceOut *) hw;
433     struct oss_params req, obt;
434     int endianness;
435     int err;
436     int fd;
437     audfmt_e effective_fmt;
438     audsettings_t obt_as;
439
440     oss->fd = -1;
441
442     req.fmt = aud_to_ossfmt (as->fmt);
443     req.freq = as->freq;
444     req.nchannels = as->nchannels;
445     req.fragsize = conf.fragsize;
446     req.nfrags = conf.nfrags;
447
448     if (oss_open (0, &req, &obt, &fd)) {
449         return -1;
450     }
451
452     err = oss_to_audfmt (obt.fmt, &effective_fmt, &endianness);
453     if (err) {
454         oss_anal_close (&fd);
455         return -1;
456     }
457
458     obt_as.freq = obt.freq;
459     obt_as.nchannels = obt.nchannels;
460     obt_as.fmt = effective_fmt;
461     obt_as.endianness = endianness;
462
463     audio_pcm_init_info (&hw->info, &obt_as);
464     oss->nfrags = obt.nfrags;
465     oss->fragsize = obt.fragsize;
466
467     if (obt.nfrags * obt.fragsize & hw->info.align) {
468         dolog ("warning: Misaligned DAC buffer, size %d, alignment %d\n",
469                obt.nfrags * obt.fragsize, hw->info.align + 1);
470     }
471
472     hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift;
473
474     oss->mmapped = 0;
475     if (conf.try_mmap) {
476         oss->pcm_buf = mmap (
477             0,
478             hw->samples << hw->info.shift,
479             PROT_READ | PROT_WRITE,
480             MAP_SHARED,
481             fd,
482             0
483             );
484         if (oss->pcm_buf == MAP_FAILED) {
485             oss_logerr (errno, "Failed to map %d bytes of DAC\n",
486                         hw->samples << hw->info.shift);
487         } else {
488             int err;
489             int trig = 0;
490             if (ioctl (fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) {
491                 oss_logerr (errno, "SNDCTL_DSP_SETTRIGGER 0 failed\n");
492             }
493             else {
494                 trig = PCM_ENABLE_OUTPUT;
495                 if (ioctl (fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) {
496                     oss_logerr (
497                         errno,
498                         "SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n"
499                         );
500                 }
501                 else {
502                     oss->mmapped = 1;
503                 }
504             }
505
506             if (!oss->mmapped) {
507                 err = munmap (oss->pcm_buf, hw->samples << hw->info.shift);
508                 if (err) {
509                     oss_logerr (errno, "Failed to unmap buffer %p size %d\n",
510                                 oss->pcm_buf, hw->samples << hw->info.shift);
511                 }
512             }
513         }
514     }
515
516     if (!oss->mmapped) {
517         oss->pcm_buf = audio_calloc (
518             AUDIO_FUNC,
519             hw->samples,
520             1 << hw->info.shift
521             );
522         if (!oss->pcm_buf) {
523             dolog (
524                 "Could not allocate DAC buffer (%d samples, each %d bytes)\n",
525                 hw->samples,
526                 1 << hw->info.shift
527                 );
528             oss_anal_close (&fd);
529             return -1;
530         }
531     }
532
533     oss->fd = fd;
534     return 0;
535 }
536
537 static int oss_ctl_out (HWVoiceOut *hw, int cmd, ...)
538 {
539     int trig;
540     OSSVoiceOut *oss = (OSSVoiceOut *) hw;
541
542     if (!oss->mmapped) {
543         return 0;
544     }
545
546     switch (cmd) {
547     case VOICE_ENABLE:
548         ldebug ("enabling voice\n");
549         audio_pcm_info_clear_buf (&hw->info, oss->pcm_buf, hw->samples);
550         trig = PCM_ENABLE_OUTPUT;
551         if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) {
552             oss_logerr (
553                 errno,
554                 "SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n"
555                 );
556             return -1;
557         }
558         break;
559
560     case VOICE_DISABLE:
561         ldebug ("disabling voice\n");
562         trig = 0;
563         if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) {
564             oss_logerr (errno, "SNDCTL_DSP_SETTRIGGER 0 failed\n");
565             return -1;
566         }
567         break;
568     }
569     return 0;
570 }
571
572 static int oss_init_in (HWVoiceIn *hw, audsettings_t *as)
573 {
574     OSSVoiceIn *oss = (OSSVoiceIn *) hw;
575     struct oss_params req, obt;
576     int endianness;
577     int err;
578     int fd;
579     audfmt_e effective_fmt;
580     audsettings_t obt_as;
581
582     oss->fd = -1;
583
584     req.fmt = aud_to_ossfmt (as->fmt);
585     req.freq = as->freq;
586     req.nchannels = as->nchannels;
587     req.fragsize = conf.fragsize;
588     req.nfrags = conf.nfrags;
589     if (oss_open (1, &req, &obt, &fd)) {
590         return -1;
591     }
592
593     err = oss_to_audfmt (obt.fmt, &effective_fmt, &endianness);
594     if (err) {
595         oss_anal_close (&fd);
596         return -1;
597     }
598
599     obt_as.freq = obt.freq;
600     obt_as.nchannels = obt.nchannels;
601     obt_as.fmt = effective_fmt;
602     obt_as.endianness = endianness;
603
604     audio_pcm_init_info (&hw->info, &obt_as);
605     oss->nfrags = obt.nfrags;
606     oss->fragsize = obt.fragsize;
607
608     if (obt.nfrags * obt.fragsize & hw->info.align) {
609         dolog ("warning: Misaligned ADC buffer, size %d, alignment %d\n",
610                obt.nfrags * obt.fragsize, hw->info.align + 1);
611     }
612
613     hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift;
614     oss->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
615     if (!oss->pcm_buf) {
616         dolog ("Could not allocate ADC buffer (%d samples, each %d bytes)\n",
617                hw->samples, 1 << hw->info.shift);
618         oss_anal_close (&fd);
619         return -1;
620     }
621
622     oss->fd = fd;
623     return 0;
624 }
625
626 static void oss_fini_in (HWVoiceIn *hw)
627 {
628     OSSVoiceIn *oss = (OSSVoiceIn *) hw;
629
630     oss_anal_close (&oss->fd);
631
632     if (oss->pcm_buf) {
633         qemu_free (oss->pcm_buf);
634         oss->pcm_buf = NULL;
635     }
636 }
637
638 static int oss_run_in (HWVoiceIn *hw)
639 {
640     OSSVoiceIn *oss = (OSSVoiceIn *) hw;
641     int hwshift = hw->info.shift;
642     int i;
643     int live = audio_pcm_hw_get_live_in (hw);
644     int dead = hw->samples - live;
645     size_t read_samples = 0;
646     struct {
647         int add;
648         int len;
649     } bufs[2] = {
650         { hw->wpos, 0 },
651         { 0, 0 }
652     };
653
654     if (!dead) {
655         return 0;
656     }
657
658     if (hw->wpos + dead > hw->samples) {
659         bufs[0].len = (hw->samples - hw->wpos) << hwshift;
660         bufs[1].len = (dead - (hw->samples - hw->wpos)) << hwshift;
661     }
662     else {
663         bufs[0].len = dead << hwshift;
664     }
665
666
667     for (i = 0; i < 2; ++i) {
668         ssize_t nread;
669
670         if (bufs[i].len) {
671             void *p = advance (oss->pcm_buf, bufs[i].add << hwshift);
672             nread = read (oss->fd, p, bufs[i].len);
673
674             if (nread > 0) {
675                 if (nread & hw->info.align) {
676                     dolog ("warning: Misaligned read %zd (requested %d), "
677                            "alignment %d\n", nread, bufs[i].add << hwshift,
678                            hw->info.align + 1);
679                 }
680                 read_samples += nread >> hwshift;
681                 hw->conv (hw->conv_buf + bufs[i].add, p, nread >> hwshift,
682                           &nominal_volume);
683             }
684
685             if (bufs[i].len - nread) {
686                 if (nread == -1) {
687                     switch (errno) {
688                     case EINTR:
689                     case EAGAIN:
690                         break;
691                     default:
692                         oss_logerr (
693                             errno,
694                             "Failed to read %d bytes of audio (to %p)\n",
695                             bufs[i].len, p
696                             );
697                         break;
698                     }
699                 }
700                 break;
701             }
702         }
703     }
704
705     hw->wpos = (hw->wpos + read_samples) % hw->samples;
706     return read_samples;
707 }
708
709 static int oss_read (SWVoiceIn *sw, void *buf, int size)
710 {
711     return audio_pcm_sw_read (sw, buf, size);
712 }
713
714 static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...)
715 {
716     (void) hw;
717     (void) cmd;
718     return 0;
719 }
720
721 static void *oss_audio_init (void)
722 {
723     return &conf;
724 }
725
726 static void oss_audio_fini (void *opaque)
727 {
728     (void) opaque;
729 }
730
731 static struct audio_option oss_options[] = {
732     {"FRAGSIZE", AUD_OPT_INT, &conf.fragsize,
733      "Fragment size in bytes", NULL, 0},
734     {"NFRAGS", AUD_OPT_INT, &conf.nfrags,
735      "Number of fragments", NULL, 0},
736     {"MMAP", AUD_OPT_BOOL, &conf.try_mmap,
737      "Try using memory mapped access", NULL, 0},
738     {"DAC_DEV", AUD_OPT_STR, &conf.devpath_out,
739      "Path to DAC device", NULL, 0},
740     {"ADC_DEV", AUD_OPT_STR, &conf.devpath_in,
741      "Path to ADC device", NULL, 0},
742     {"DEBUG", AUD_OPT_BOOL, &conf.debug,
743      "Turn on some debugging messages", NULL, 0},
744     {NULL, 0, NULL, NULL, NULL, 0}
745 };
746
747 static struct audio_pcm_ops oss_pcm_ops = {
748     oss_init_out,
749     oss_fini_out,
750     oss_run_out,
751     oss_write,
752     oss_ctl_out,
753
754     oss_init_in,
755     oss_fini_in,
756     oss_run_in,
757     oss_read,
758     oss_ctl_in
759 };
760
761 struct audio_driver oss_audio_driver = {
762     INIT_FIELD (name           = ) "oss",
763     INIT_FIELD (descr          = ) "OSS http://www.opensound.com",
764     INIT_FIELD (options        = ) oss_options,
765     INIT_FIELD (init           = ) oss_audio_init,
766     INIT_FIELD (fini           = ) oss_audio_fini,
767     INIT_FIELD (pcm_ops        = ) &oss_pcm_ops,
768     INIT_FIELD (can_be_default = ) 1,
769     INIT_FIELD (max_voices_out = ) INT_MAX,
770     INIT_FIELD (max_voices_in  = ) INT_MAX,
771     INIT_FIELD (voice_size_out = ) sizeof (OSSVoiceOut),
772     INIT_FIELD (voice_size_in  = ) sizeof (OSSVoiceIn)
773 };