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