root / audio / ossaudio.c @ fb065187
History | View | Annotate | Download (12.7 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 |
#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 |
}; |