audio clean up (initial patch by malc)
[qemu] / audio / ossaudio.c
1 /*
2  * QEMU OSS audio output driver
3  * 
4  * Copyright (c) 2003-2004 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 <sys/mman.h>
25 #include <sys/types.h>
26 #include <sys/ioctl.h>
27 #include <sys/soundcard.h>
28 #include <assert.h>
29 #include "vl.h"
30
31 #include "audio/audio_int.h"
32
33 typedef struct OSSVoice {
34     HWVoice hw;
35     void *pcm_buf;
36     int fd;
37     int nfrags;
38     int fragsize;
39     int mmapped;
40     int old_optr;
41 } OSSVoice;
42
43
44 #define dolog(...) AUD_log ("oss", __VA_ARGS__)
45 #ifdef DEBUG
46 #define ldebug(...) dolog (__VA_ARGS__)
47 #else
48 #define ldebug(...)
49 #endif
50
51 #define QC_OSS_FRAGSIZE "QEMU_OSS_FRAGSIZE"
52 #define QC_OSS_NFRAGS   "QEMU_OSS_NFRAGS"
53 #define QC_OSS_MMAP     "QEMU_OSS_MMAP"
54 #define QC_OSS_DEV      "QEMU_OSS_DEV"
55
56 #define errstr() strerror (errno)
57
58 static struct {
59     int try_mmap;
60     int nfrags;
61     int fragsize;
62     const char *dspname;
63 } conf = {
64     .try_mmap = 0,
65     .nfrags = 4,
66     .fragsize = 4096,
67     .dspname = "/dev/dsp"
68 };
69
70 struct oss_params {
71     int freq;
72     audfmt_e fmt;
73     int nchannels;
74     int nfrags;
75     int fragsize;
76 };
77
78 static int oss_hw_write (SWVoice *sw, void *buf, int len)
79 {
80     return pcm_hw_write (sw, buf, len);
81 }
82
83 static int AUD_to_ossfmt (audfmt_e fmt)
84 {
85     switch (fmt) {
86     case AUD_FMT_S8: return AFMT_S8;
87     case AUD_FMT_U8: return AFMT_U8;
88     case AUD_FMT_S16: return AFMT_S16_LE;
89     case AUD_FMT_U16: return AFMT_U16_LE;
90     default:
91         dolog ("Internal logic error: Bad audio format %d\nAborting\n", fmt);
92         exit (EXIT_FAILURE);
93     }
94 }
95
96 static int oss_to_audfmt (int fmt)
97 {
98     switch (fmt) {
99     case AFMT_S8: return AUD_FMT_S8;
100     case AFMT_U8: return AUD_FMT_U8;
101     case AFMT_S16_LE: return AUD_FMT_S16;
102     case AFMT_U16_LE: return AUD_FMT_U16;
103     default:
104         dolog ("Internal logic error: Unrecognized OSS audio format %d\n"
105                "Aborting\n",
106                fmt);
107         exit (EXIT_FAILURE);
108     }
109 }
110
111 #ifdef DEBUG_PCM
112 static void oss_dump_pcm_info (struct oss_params *req, struct oss_params *obt)
113 {
114     dolog ("parameter | requested value | obtained value\n");
115     dolog ("format    |      %10d |     %10d\n", req->fmt, obt->fmt);
116     dolog ("channels  |      %10d |     %10d\n", req->nchannels, obt->nchannels);
117     dolog ("frequency |      %10d |     %10d\n", req->freq, obt->freq);
118     dolog ("nfrags    |      %10d |     %10d\n", req->nfrags, obt->nfrags);
119     dolog ("fragsize  |      %10d |     %10d\n", req->fragsize, obt->fragsize);
120 }
121 #endif
122
123 static int oss_open (struct oss_params *req, struct oss_params *obt, int *pfd)
124 {
125     int fd;
126     int mmmmssss;
127     audio_buf_info abinfo;
128     int fmt, freq, nchannels;
129     const char *dspname = conf.dspname;
130
131     fd = open (dspname, O_RDWR | O_NONBLOCK);
132     if (-1 == fd) {
133         dolog ("Could not initialize audio hardware. Failed to open `%s':\n"
134                "Reason:%s\n",
135                dspname,
136                errstr ());
137         return -1;
138     }
139
140     freq = req->freq;
141     nchannels = req->nchannels;
142     fmt = req->fmt;
143
144     if (ioctl (fd, SNDCTL_DSP_SAMPLESIZE, &fmt)) {
145         dolog ("Could not initialize audio hardware\n"
146                "Failed to set sample size\n"
147                "Reason: %s\n",
148                errstr ());
149         goto err;
150     }
151
152     if (ioctl (fd, SNDCTL_DSP_CHANNELS, &nchannels)) {
153         dolog ("Could not initialize audio hardware\n"
154                "Failed to set number of channels\n"
155                "Reason: %s\n",
156                errstr ());
157         goto err;
158     }
159
160     if (ioctl (fd, SNDCTL_DSP_SPEED, &freq)) {
161         dolog ("Could not initialize audio hardware\n"
162                "Failed to set frequency\n"
163                "Reason: %s\n",
164                errstr ());
165         goto err;
166     }
167
168     if (ioctl (fd, SNDCTL_DSP_NONBLOCK)) {
169         dolog ("Could not initialize audio hardware\n"
170                "Failed to set non-blocking mode\n"
171                "Reason: %s\n",
172                errstr ());
173         goto err;
174     }
175
176     mmmmssss = (req->nfrags << 16) | lsbindex (req->fragsize);
177     if (ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &mmmmssss)) {
178         dolog ("Could not initialize audio hardware\n"
179                "Failed to set buffer length (%d, %d)\n"
180                "Reason:%s\n",
181                conf.nfrags, conf.fragsize,
182                errstr ());
183         goto err;
184     }
185
186     if (ioctl (fd, SNDCTL_DSP_GETOSPACE, &abinfo)) {
187         dolog ("Could not initialize audio hardware\n"
188                "Failed to get buffer length\n"
189                "Reason:%s\n",
190                errstr ());
191         goto err;
192     }
193
194     obt->fmt = fmt;
195     obt->nchannels = nchannels;
196     obt->freq = freq;
197     obt->nfrags = abinfo.fragstotal;
198     obt->fragsize = abinfo.fragsize;
199     *pfd = fd;
200
201     if ((req->fmt != obt->fmt) ||
202         (req->nchannels != obt->nchannels) ||
203         (req->freq != obt->freq) ||
204         (req->fragsize != obt->fragsize) ||
205         (req->nfrags != obt->nfrags)) {
206 #ifdef DEBUG_PCM
207         dolog ("Audio parameters mismatch\n");
208         oss_dump_pcm_info (req, obt);
209 #endif
210     }
211
212 #ifdef DEBUG_PCM
213     oss_dump_pcm_info (req, obt);
214 #endif
215     return 0;
216
217 err:
218     close (fd);
219     return -1;
220 }
221
222 static void oss_hw_run (HWVoice *hw)
223 {
224     OSSVoice *oss = (OSSVoice *) hw;
225     int err, rpos, live, decr;
226     int samples;
227     uint8_t *dst;
228     st_sample_t *src;
229     struct audio_buf_info abinfo;
230     struct count_info cntinfo;
231
232     live = pcm_hw_get_live (hw);
233     if (live <= 0)
234         return;
235
236     if (oss->mmapped) {
237         int bytes;
238
239         err = ioctl (oss->fd, SNDCTL_DSP_GETOPTR, &cntinfo);
240         if (err < 0) {
241             dolog ("SNDCTL_DSP_GETOPTR failed\nReason: %s\n", errstr ());
242             return;
243         }
244
245         if (cntinfo.ptr == oss->old_optr) {
246             if (abs (hw->samples - live) < 64)
247                 dolog ("overrun\n");
248             return;
249         }
250
251         if (cntinfo.ptr > oss->old_optr) {
252             bytes = cntinfo.ptr - oss->old_optr;
253         }
254         else {
255             bytes = hw->bufsize + cntinfo.ptr - oss->old_optr;
256         }
257
258         decr = audio_MIN (bytes >> hw->shift, live);
259     }
260     else {
261         err = ioctl (oss->fd, SNDCTL_DSP_GETOSPACE, &abinfo);
262         if (err < 0) {
263             dolog ("SNDCTL_DSP_GETOSPACE failed\nReason: %s\n", errstr ());
264             return;
265         }
266
267         decr = audio_MIN (abinfo.bytes >> hw->shift, live);
268         if (decr <= 0)
269             return;
270     }
271
272     samples = decr;
273     rpos = hw->rpos;
274     while (samples) {
275         int left_till_end_samples = hw->samples - rpos;
276         int convert_samples = audio_MIN (samples, left_till_end_samples);
277
278         src = advance (hw->mix_buf, rpos * sizeof (st_sample_t));
279         dst = advance (oss->pcm_buf, rpos << hw->shift);
280
281         hw->clip (dst, src, convert_samples);
282         if (!oss->mmapped) {
283             int written;
284
285             written = write (oss->fd, dst, convert_samples << hw->shift);
286             /* XXX: follow errno recommendations ? */
287             if (written == -1) {
288                 dolog ("Failed to write audio\nReason: %s\n", errstr ());
289                 continue;
290             }
291
292             if (written != convert_samples << hw->shift) {
293                 int wsamples = written >> hw->shift;
294                 int wbytes = wsamples << hw->shift;
295                 if (wbytes != written) {
296                     dolog ("Unaligned write %d, %d\n", wbytes, written);
297                 }
298                 memset (src, 0, wbytes);
299                 decr -= samples;
300                 rpos = (rpos + wsamples) % hw->samples;
301                 break;
302             }
303         }
304         memset (src, 0, convert_samples * sizeof (st_sample_t));
305
306         rpos = (rpos + convert_samples) % hw->samples;
307         samples -= convert_samples;
308     }
309     if (oss->mmapped) {
310         oss->old_optr = cntinfo.ptr;
311     }
312
313     pcm_hw_dec_live (hw, decr);
314     hw->rpos = rpos;
315 }
316
317 static void oss_hw_fini (HWVoice *hw)
318 {
319     int err;
320     OSSVoice *oss = (OSSVoice *) hw;
321
322     ldebug ("oss_hw_fini\n");
323     err = close (oss->fd);
324     if (err) {
325         dolog ("Failed to close OSS descriptor\nReason: %s\n", errstr ());
326     }
327     oss->fd = -1;
328
329     if (oss->pcm_buf) {
330         if (oss->mmapped) {
331             err = munmap (oss->pcm_buf, hw->bufsize);
332             if (err) {
333                 dolog ("Failed to unmap OSS buffer\nReason: %s\n",
334                        errstr ());
335             }
336         }
337         else {
338             qemu_free (oss->pcm_buf);
339         }
340         oss->pcm_buf = NULL;
341     }
342 }
343
344 static int oss_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt)
345 {
346     OSSVoice *oss = (OSSVoice *) hw;
347     struct oss_params req, obt;
348
349     assert (!oss->fd);
350     req.fmt = AUD_to_ossfmt (fmt);
351     req.freq = freq;
352     req.nchannels = nchannels;
353     req.fragsize = conf.fragsize;
354     req.nfrags = conf.nfrags;
355
356     if (oss_open (&req, &obt, &oss->fd))
357         return -1;
358
359     hw->freq = obt.freq;
360     hw->fmt = oss_to_audfmt (obt.fmt);
361     hw->nchannels = obt.nchannels;
362
363     oss->nfrags = obt.nfrags;
364     oss->fragsize = obt.fragsize;
365     hw->bufsize = obt.nfrags * obt.fragsize;
366
367     oss->mmapped = 0;
368     if (conf.try_mmap) {
369         oss->pcm_buf = mmap (0, hw->bufsize, PROT_READ | PROT_WRITE,
370                              MAP_SHARED, oss->fd, 0);
371         if (oss->pcm_buf == MAP_FAILED) {
372             dolog ("Failed to mmap OSS device\nReason: %s\n",
373                    errstr ());
374         }
375
376         for (;;) {
377             int err;
378             int trig = 0;
379             if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) {
380                 dolog ("SNDCTL_DSP_SETTRIGGER 0 failed\nReason: %s\n",
381                        errstr ());
382                 goto fail;
383             }
384
385             trig = PCM_ENABLE_OUTPUT;
386             if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) {
387                 dolog ("SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n"
388                        "Reason: %s\n", errstr ());
389                 goto fail;
390             }
391             oss->mmapped = 1;
392             break;
393
394         fail:
395             err = munmap (oss->pcm_buf, hw->bufsize);
396             if (err) {
397                 dolog ("Failed to unmap OSS device\nReason: %s\n",
398                        errstr ());
399             }
400         }
401     }
402
403     if (!oss->mmapped) {
404         oss->pcm_buf = qemu_mallocz (hw->bufsize);
405         if (!oss->pcm_buf) {
406             close (oss->fd);
407             oss->fd = -1;
408             return -1;
409         }
410     }
411
412     return 0;
413 }
414
415 static int oss_hw_ctl (HWVoice *hw, int cmd, ...)
416 {
417     int trig;
418     OSSVoice *oss = (OSSVoice *) hw;
419
420     if (!oss->mmapped)
421         return 0;
422
423     switch (cmd) {
424     case VOICE_ENABLE:
425         ldebug ("enabling voice\n");
426         pcm_hw_clear (hw, oss->pcm_buf, hw->samples);
427         trig = PCM_ENABLE_OUTPUT;
428         if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) {
429             dolog ("SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n"
430                    "Reason: %s\n", errstr ());
431             return -1;
432         }
433         break;
434
435     case VOICE_DISABLE:
436         ldebug ("disabling voice\n");
437         trig = 0;
438         if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) {
439             dolog ("SNDCTL_DSP_SETTRIGGER 0 failed\nReason: %s\n",
440                    errstr ());
441             return -1;
442         }
443         break;
444     }
445     return 0;
446 }
447
448 static void *oss_audio_init (void)
449 {
450     conf.fragsize = audio_get_conf_int (QC_OSS_FRAGSIZE, conf.fragsize);
451     conf.nfrags = audio_get_conf_int (QC_OSS_NFRAGS, conf.nfrags);
452     conf.try_mmap = audio_get_conf_int (QC_OSS_MMAP, conf.try_mmap);
453     conf.dspname = audio_get_conf_str (QC_OSS_DEV, conf.dspname);
454     return &conf;
455 }
456
457 static void oss_audio_fini (void *opaque)
458 {
459 }
460
461 struct pcm_ops oss_pcm_ops = {
462     oss_hw_init,
463     oss_hw_fini,
464     oss_hw_run,
465     oss_hw_write,
466     oss_hw_ctl
467 };
468
469 struct audio_output_driver oss_output_driver = {
470     "oss",
471     oss_audio_init,
472     oss_audio_fini,
473     &oss_pcm_ops,
474     1,
475     INT_MAX,
476     sizeof (OSSVoice)
477 };