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