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 | |
25 | #include "qemu/osdep.h" |
26 | #include <sys/ioctl.h> |
27 | #include <sys/soundcard.h> |
28 | #include "qemu/main-loop.h" |
29 | #include "qemu/module.h" |
30 | #include "qemu/host-utils.h" |
31 | #include "audio.h" |
32 | #include "trace.h" |
33 | |
34 | #define AUDIO_CAP "oss" |
35 | #include "audio_int.h" |
36 | |
37 | #if defined OSS_GETVERSION && defined SNDCTL_DSP_POLICY |
38 | #define USE_DSP_POLICY |
39 | #endif |
40 | |
41 | typedef struct OSSVoiceOut { |
42 | HWVoiceOut hw; |
43 | void *pcm_buf; |
44 | int fd; |
45 | int wpos; |
46 | int nfrags; |
47 | int fragsize; |
48 | int mmapped; |
49 | int pending; |
50 | Audiodev *dev; |
51 | } OSSVoiceOut; |
52 | |
53 | typedef struct OSSVoiceIn { |
54 | HWVoiceIn hw; |
55 | void *pcm_buf; |
56 | int fd; |
57 | int nfrags; |
58 | int fragsize; |
59 | Audiodev *dev; |
60 | } OSSVoiceIn; |
61 | |
62 | struct oss_params { |
63 | int freq; |
64 | int fmt; |
65 | int nchannels; |
66 | int nfrags; |
67 | int fragsize; |
68 | }; |
69 | |
70 | static void GCC_FMT_ATTR (2, 3) oss_logerr (int err, const char *fmt, ...) |
71 | { |
72 | va_list ap; |
73 | |
74 | va_start (ap, fmt); |
75 | AUD_vlog (AUDIO_CAP, fmt, ap); |
76 | va_end (ap); |
77 | |
78 | AUD_log (AUDIO_CAP, "Reason: %s\n" , strerror (err)); |
79 | } |
80 | |
81 | static void GCC_FMT_ATTR (3, 4) oss_logerr2 ( |
82 | int err, |
83 | const char *typ, |
84 | const char *fmt, |
85 | ... |
86 | ) |
87 | { |
88 | va_list ap; |
89 | |
90 | AUD_log (AUDIO_CAP, "Could not initialize %s\n" , typ); |
91 | |
92 | va_start (ap, fmt); |
93 | AUD_vlog (AUDIO_CAP, fmt, ap); |
94 | va_end (ap); |
95 | |
96 | AUD_log (AUDIO_CAP, "Reason: %s\n" , strerror (err)); |
97 | } |
98 | |
99 | static void oss_anal_close (int *fdp) |
100 | { |
101 | int err; |
102 | |
103 | qemu_set_fd_handler (*fdp, NULL, NULL, NULL); |
104 | err = close (*fdp); |
105 | if (err) { |
106 | oss_logerr (errno, "Failed to close file(fd=%d)\n" , *fdp); |
107 | } |
108 | *fdp = -1; |
109 | } |
110 | |
111 | static void oss_helper_poll_out (void *opaque) |
112 | { |
113 | AudioState *s = opaque; |
114 | audio_run(s, "oss_poll_out" ); |
115 | } |
116 | |
117 | static void oss_helper_poll_in (void *opaque) |
118 | { |
119 | AudioState *s = opaque; |
120 | audio_run(s, "oss_poll_in" ); |
121 | } |
122 | |
123 | static void oss_poll_out (HWVoiceOut *hw) |
124 | { |
125 | OSSVoiceOut *oss = (OSSVoiceOut *) hw; |
126 | |
127 | qemu_set_fd_handler(oss->fd, NULL, oss_helper_poll_out, hw->s); |
128 | } |
129 | |
130 | static void oss_poll_in (HWVoiceIn *hw) |
131 | { |
132 | OSSVoiceIn *oss = (OSSVoiceIn *) hw; |
133 | |
134 | qemu_set_fd_handler(oss->fd, oss_helper_poll_in, NULL, hw->s); |
135 | } |
136 | |
137 | static int aud_to_ossfmt (AudioFormat fmt, int endianness) |
138 | { |
139 | switch (fmt) { |
140 | case AUDIO_FORMAT_S8: |
141 | return AFMT_S8; |
142 | |
143 | case AUDIO_FORMAT_U8: |
144 | return AFMT_U8; |
145 | |
146 | case AUDIO_FORMAT_S16: |
147 | if (endianness) { |
148 | return AFMT_S16_BE; |
149 | } |
150 | else { |
151 | return AFMT_S16_LE; |
152 | } |
153 | |
154 | case AUDIO_FORMAT_U16: |
155 | if (endianness) { |
156 | return AFMT_U16_BE; |
157 | } |
158 | else { |
159 | return AFMT_U16_LE; |
160 | } |
161 | |
162 | default: |
163 | dolog ("Internal logic error: Bad audio format %d\n" , fmt); |
164 | #ifdef DEBUG_AUDIO |
165 | abort (); |
166 | #endif |
167 | return AFMT_U8; |
168 | } |
169 | } |
170 | |
171 | static int oss_to_audfmt (int ossfmt, AudioFormat *fmt, int *endianness) |
172 | { |
173 | switch (ossfmt) { |
174 | case AFMT_S8: |
175 | *endianness = 0; |
176 | *fmt = AUDIO_FORMAT_S8; |
177 | break; |
178 | |
179 | case AFMT_U8: |
180 | *endianness = 0; |
181 | *fmt = AUDIO_FORMAT_U8; |
182 | break; |
183 | |
184 | case AFMT_S16_LE: |
185 | *endianness = 0; |
186 | *fmt = AUDIO_FORMAT_S16; |
187 | break; |
188 | |
189 | case AFMT_U16_LE: |
190 | *endianness = 0; |
191 | *fmt = AUDIO_FORMAT_U16; |
192 | break; |
193 | |
194 | case AFMT_S16_BE: |
195 | *endianness = 1; |
196 | *fmt = AUDIO_FORMAT_S16; |
197 | break; |
198 | |
199 | case AFMT_U16_BE: |
200 | *endianness = 1; |
201 | *fmt = AUDIO_FORMAT_U16; |
202 | break; |
203 | |
204 | default: |
205 | dolog ("Unrecognized audio format %d\n" , ossfmt); |
206 | return -1; |
207 | } |
208 | |
209 | return 0; |
210 | } |
211 | |
212 | #if defined DEBUG_MISMATCHES || defined DEBUG |
213 | static void oss_dump_info (struct oss_params *req, struct oss_params *obt) |
214 | { |
215 | dolog ("parameter | requested value | obtained value\n" ); |
216 | dolog ("format | %10d | %10d\n" , req->fmt, obt->fmt); |
217 | dolog ("channels | %10d | %10d\n" , |
218 | req->nchannels, obt->nchannels); |
219 | dolog ("frequency | %10d | %10d\n" , req->freq, obt->freq); |
220 | dolog ("nfrags | %10d | %10d\n" , req->nfrags, obt->nfrags); |
221 | dolog ("fragsize | %10d | %10d\n" , |
222 | req->fragsize, obt->fragsize); |
223 | } |
224 | #endif |
225 | |
226 | #ifdef USE_DSP_POLICY |
227 | static int oss_get_version (int fd, int *version, const char *typ) |
228 | { |
229 | if (ioctl (fd, OSS_GETVERSION, &version)) { |
230 | #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) |
231 | /* |
232 | * Looks like atm (20100109) FreeBSD knows OSS_GETVERSION |
233 | * since 7.x, but currently only on the mixer device (or in |
234 | * the Linuxolator), and in the native version that part of |
235 | * the code is in fact never reached so the ioctl fails anyway. |
236 | * Until this is fixed, just check the errno and if its what |
237 | * FreeBSD's sound drivers return atm assume they are new enough. |
238 | */ |
239 | if (errno == EINVAL) { |
240 | *version = 0x040000; |
241 | return 0; |
242 | } |
243 | #endif |
244 | oss_logerr2 (errno, typ, "Failed to get OSS version\n" ); |
245 | return -1; |
246 | } |
247 | return 0; |
248 | } |
249 | #endif |
250 | |
251 | static int oss_open(int in, struct oss_params *req, audsettings *as, |
252 | struct oss_params *obt, int *pfd, Audiodev *dev) |
253 | { |
254 | AudiodevOssOptions *oopts = &dev->u.oss; |
255 | AudiodevOssPerDirectionOptions *opdo = in ? oopts->in : oopts->out; |
256 | int fd; |
257 | int oflags = (oopts->has_exclusive && oopts->exclusive) ? O_EXCL : 0; |
258 | audio_buf_info abinfo; |
259 | int fmt, freq, nchannels; |
260 | int setfragment = 1; |
261 | const char *dspname = opdo->has_dev ? opdo->dev : "/dev/dsp" ; |
262 | const char *typ = in ? "ADC" : "DAC" ; |
263 | #ifdef USE_DSP_POLICY |
264 | int policy = oopts->has_dsp_policy ? oopts->dsp_policy : 5; |
265 | #endif |
266 | |
267 | /* Kludge needed to have working mmap on Linux */ |
268 | oflags |= (oopts->has_try_mmap && oopts->try_mmap) ? |
269 | O_RDWR : (in ? O_RDONLY : O_WRONLY); |
270 | |
271 | fd = open (dspname, oflags | O_NONBLOCK); |
272 | if (-1 == fd) { |
273 | oss_logerr2 (errno, typ, "Failed to open `%s'\n" , dspname); |
274 | return -1; |
275 | } |
276 | |
277 | freq = req->freq; |
278 | nchannels = req->nchannels; |
279 | fmt = req->fmt; |
280 | req->nfrags = opdo->has_buffer_count ? opdo->buffer_count : 4; |
281 | req->fragsize = audio_buffer_bytes( |
282 | qapi_AudiodevOssPerDirectionOptions_base(opdo), as, 23220); |
283 | |
284 | if (ioctl (fd, SNDCTL_DSP_SAMPLESIZE, &fmt)) { |
285 | oss_logerr2 (errno, typ, "Failed to set sample size %d\n" , req->fmt); |
286 | goto err; |
287 | } |
288 | |
289 | if (ioctl (fd, SNDCTL_DSP_CHANNELS, &nchannels)) { |
290 | oss_logerr2 (errno, typ, "Failed to set number of channels %d\n" , |
291 | req->nchannels); |
292 | goto err; |
293 | } |
294 | |
295 | if (ioctl (fd, SNDCTL_DSP_SPEED, &freq)) { |
296 | oss_logerr2 (errno, typ, "Failed to set frequency %d\n" , req->freq); |
297 | goto err; |
298 | } |
299 | |
300 | if (ioctl (fd, SNDCTL_DSP_NONBLOCK, NULL)) { |
301 | oss_logerr2 (errno, typ, "Failed to set non-blocking mode\n" ); |
302 | goto err; |
303 | } |
304 | |
305 | #ifdef USE_DSP_POLICY |
306 | if (policy >= 0) { |
307 | int version; |
308 | |
309 | if (!oss_get_version (fd, &version, typ)) { |
310 | trace_oss_version(version); |
311 | |
312 | if (version >= 0x040000) { |
313 | int policy2 = policy; |
314 | if (ioctl(fd, SNDCTL_DSP_POLICY, &policy2)) { |
315 | oss_logerr2 (errno, typ, |
316 | "Failed to set timing policy to %d\n" , |
317 | policy); |
318 | goto err; |
319 | } |
320 | setfragment = 0; |
321 | } |
322 | } |
323 | } |
324 | #endif |
325 | |
326 | if (setfragment) { |
327 | int mmmmssss = (req->nfrags << 16) | ctz32 (req->fragsize); |
328 | if (ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &mmmmssss)) { |
329 | oss_logerr2 (errno, typ, "Failed to set buffer length (%d, %d)\n" , |
330 | req->nfrags, req->fragsize); |
331 | goto err; |
332 | } |
333 | } |
334 | |
335 | if (ioctl (fd, in ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &abinfo)) { |
336 | oss_logerr2 (errno, typ, "Failed to get buffer length\n" ); |
337 | goto err; |
338 | } |
339 | |
340 | if (!abinfo.fragstotal || !abinfo.fragsize) { |
341 | AUD_log (AUDIO_CAP, "Returned bogus buffer information(%d, %d) for %s\n" , |
342 | abinfo.fragstotal, abinfo.fragsize, typ); |
343 | goto err; |
344 | } |
345 | |
346 | obt->fmt = fmt; |
347 | obt->nchannels = nchannels; |
348 | obt->freq = freq; |
349 | obt->nfrags = abinfo.fragstotal; |
350 | obt->fragsize = abinfo.fragsize; |
351 | *pfd = fd; |
352 | |
353 | #ifdef DEBUG_MISMATCHES |
354 | if ((req->fmt != obt->fmt) || |
355 | (req->nchannels != obt->nchannels) || |
356 | (req->freq != obt->freq) || |
357 | (req->fragsize != obt->fragsize) || |
358 | (req->nfrags != obt->nfrags)) { |
359 | dolog ("Audio parameters mismatch\n" ); |
360 | oss_dump_info (req, obt); |
361 | } |
362 | #endif |
363 | |
364 | #ifdef DEBUG |
365 | oss_dump_info (req, obt); |
366 | #endif |
367 | return 0; |
368 | |
369 | err: |
370 | oss_anal_close (&fd); |
371 | return -1; |
372 | } |
373 | |
374 | static void oss_write_pending (OSSVoiceOut *oss) |
375 | { |
376 | HWVoiceOut *hw = &oss->hw; |
377 | |
378 | if (oss->mmapped) { |
379 | return; |
380 | } |
381 | |
382 | while (oss->pending) { |
383 | int samples_written; |
384 | ssize_t bytes_written; |
385 | int samples_till_end = hw->samples - oss->wpos; |
386 | int samples_to_write = MIN (oss->pending, samples_till_end); |
387 | int bytes_to_write = samples_to_write << hw->info.shift; |
388 | void *pcm = advance (oss->pcm_buf, oss->wpos << hw->info.shift); |
389 | |
390 | bytes_written = write (oss->fd, pcm, bytes_to_write); |
391 | if (bytes_written < 0) { |
392 | if (errno != EAGAIN) { |
393 | oss_logerr (errno, "failed to write %d bytes\n" , |
394 | bytes_to_write); |
395 | } |
396 | break; |
397 | } |
398 | |
399 | if (bytes_written & hw->info.align) { |
400 | dolog ("misaligned write asked for %d, but got %zd\n" , |
401 | bytes_to_write, bytes_written); |
402 | return; |
403 | } |
404 | |
405 | samples_written = bytes_written >> hw->info.shift; |
406 | oss->pending -= samples_written; |
407 | oss->wpos = (oss->wpos + samples_written) % hw->samples; |
408 | if (bytes_written - bytes_to_write) { |
409 | break; |
410 | } |
411 | } |
412 | } |
413 | |
414 | static size_t oss_run_out(HWVoiceOut *hw, size_t live) |
415 | { |
416 | OSSVoiceOut *oss = (OSSVoiceOut *) hw; |
417 | int err; |
418 | size_t decr; |
419 | struct audio_buf_info abinfo; |
420 | struct count_info cntinfo; |
421 | size_t bufsize; |
422 | |
423 | bufsize = hw->samples << hw->info.shift; |
424 | |
425 | if (oss->mmapped) { |
426 | int bytes, pos; |
427 | |
428 | err = ioctl (oss->fd, SNDCTL_DSP_GETOPTR, &cntinfo); |
429 | if (err < 0) { |
430 | oss_logerr (errno, "SNDCTL_DSP_GETOPTR failed\n" ); |
431 | return 0; |
432 | } |
433 | |
434 | pos = hw->rpos << hw->info.shift; |
435 | bytes = audio_ring_dist (cntinfo.ptr, pos, bufsize); |
436 | decr = MIN (bytes >> hw->info.shift, live); |
437 | } |
438 | else { |
439 | err = ioctl (oss->fd, SNDCTL_DSP_GETOSPACE, &abinfo); |
440 | if (err < 0) { |
441 | oss_logerr (errno, "SNDCTL_DSP_GETOPTR failed\n" ); |
442 | return 0; |
443 | } |
444 | |
445 | if (abinfo.bytes > bufsize) { |
446 | trace_oss_invalid_available_size(abinfo.bytes, bufsize); |
447 | abinfo.bytes = bufsize; |
448 | } |
449 | |
450 | if (abinfo.bytes < 0) { |
451 | trace_oss_invalid_available_size(abinfo.bytes, bufsize); |
452 | return 0; |
453 | } |
454 | |
455 | decr = MIN (abinfo.bytes >> hw->info.shift, live); |
456 | if (!decr) { |
457 | return 0; |
458 | } |
459 | } |
460 | |
461 | decr = audio_pcm_hw_clip_out (hw, oss->pcm_buf, decr, oss->pending); |
462 | oss->pending += decr; |
463 | oss_write_pending (oss); |
464 | |
465 | return decr; |
466 | } |
467 | |
468 | static void oss_fini_out (HWVoiceOut *hw) |
469 | { |
470 | int err; |
471 | OSSVoiceOut *oss = (OSSVoiceOut *) hw; |
472 | |
473 | ldebug ("oss_fini\n" ); |
474 | oss_anal_close (&oss->fd); |
475 | |
476 | if (oss->pcm_buf) { |
477 | if (oss->mmapped) { |
478 | err = munmap (oss->pcm_buf, hw->samples << hw->info.shift); |
479 | if (err) { |
480 | oss_logerr(errno, "Failed to unmap buffer %p, size %zu\n" , |
481 | oss->pcm_buf, hw->samples << hw->info.shift); |
482 | } |
483 | } |
484 | else { |
485 | g_free (oss->pcm_buf); |
486 | } |
487 | oss->pcm_buf = NULL; |
488 | } |
489 | } |
490 | |
491 | static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, |
492 | void *drv_opaque) |
493 | { |
494 | OSSVoiceOut *oss = (OSSVoiceOut *) hw; |
495 | struct oss_params req, obt; |
496 | int endianness; |
497 | int err; |
498 | int fd; |
499 | AudioFormat effective_fmt; |
500 | struct audsettings obt_as; |
501 | Audiodev *dev = drv_opaque; |
502 | AudiodevOssOptions *oopts = &dev->u.oss; |
503 | |
504 | oss->fd = -1; |
505 | |
506 | req.fmt = aud_to_ossfmt (as->fmt, as->endianness); |
507 | req.freq = as->freq; |
508 | req.nchannels = as->nchannels; |
509 | |
510 | if (oss_open(0, &req, as, &obt, &fd, dev)) { |
511 | return -1; |
512 | } |
513 | |
514 | err = oss_to_audfmt (obt.fmt, &effective_fmt, &endianness); |
515 | if (err) { |
516 | oss_anal_close (&fd); |
517 | return -1; |
518 | } |
519 | |
520 | obt_as.freq = obt.freq; |
521 | obt_as.nchannels = obt.nchannels; |
522 | obt_as.fmt = effective_fmt; |
523 | obt_as.endianness = endianness; |
524 | |
525 | audio_pcm_init_info (&hw->info, &obt_as); |
526 | oss->nfrags = obt.nfrags; |
527 | oss->fragsize = obt.fragsize; |
528 | |
529 | if (obt.nfrags * obt.fragsize & hw->info.align) { |
530 | dolog ("warning: Misaligned DAC buffer, size %d, alignment %d\n" , |
531 | obt.nfrags * obt.fragsize, hw->info.align + 1); |
532 | } |
533 | |
534 | hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift; |
535 | |
536 | oss->mmapped = 0; |
537 | if (oopts->has_try_mmap && oopts->try_mmap) { |
538 | oss->pcm_buf = mmap ( |
539 | NULL, |
540 | hw->samples << hw->info.shift, |
541 | PROT_READ | PROT_WRITE, |
542 | MAP_SHARED, |
543 | fd, |
544 | 0 |
545 | ); |
546 | if (oss->pcm_buf == MAP_FAILED) { |
547 | oss_logerr(errno, "Failed to map %zu bytes of DAC\n" , |
548 | hw->samples << hw->info.shift); |
549 | } |
550 | else { |
551 | int err; |
552 | int trig = 0; |
553 | if (ioctl (fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { |
554 | oss_logerr (errno, "SNDCTL_DSP_SETTRIGGER 0 failed\n" ); |
555 | } |
556 | else { |
557 | trig = PCM_ENABLE_OUTPUT; |
558 | if (ioctl (fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { |
559 | oss_logerr ( |
560 | errno, |
561 | "SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n" |
562 | ); |
563 | } |
564 | else { |
565 | oss->mmapped = 1; |
566 | } |
567 | } |
568 | |
569 | if (!oss->mmapped) { |
570 | err = munmap (oss->pcm_buf, hw->samples << hw->info.shift); |
571 | if (err) { |
572 | oss_logerr(errno, "Failed to unmap buffer %p size %zu\n" , |
573 | oss->pcm_buf, hw->samples << hw->info.shift); |
574 | } |
575 | } |
576 | } |
577 | } |
578 | |
579 | if (!oss->mmapped) { |
580 | oss->pcm_buf = audio_calloc(__func__, |
581 | hw->samples, |
582 | 1 << hw->info.shift); |
583 | if (!oss->pcm_buf) { |
584 | dolog ( |
585 | "Could not allocate DAC buffer (%zu samples, each %d bytes)\n" , |
586 | hw->samples, |
587 | 1 << hw->info.shift |
588 | ); |
589 | oss_anal_close (&fd); |
590 | return -1; |
591 | } |
592 | } |
593 | |
594 | oss->fd = fd; |
595 | oss->dev = dev; |
596 | return 0; |
597 | } |
598 | |
599 | static int oss_ctl_out (HWVoiceOut *hw, int cmd, ...) |
600 | { |
601 | int trig; |
602 | OSSVoiceOut *oss = (OSSVoiceOut *) hw; |
603 | AudiodevOssPerDirectionOptions *opdo = oss->dev->u.oss.out; |
604 | |
605 | switch (cmd) { |
606 | case VOICE_ENABLE: |
607 | { |
608 | bool poll_mode = opdo->try_poll; |
609 | |
610 | ldebug ("enabling voice\n" ); |
611 | if (poll_mode) { |
612 | oss_poll_out (hw); |
613 | poll_mode = 0; |
614 | } |
615 | hw->poll_mode = poll_mode; |
616 | |
617 | if (!oss->mmapped) { |
618 | return 0; |
619 | } |
620 | |
621 | audio_pcm_info_clear_buf (&hw->info, oss->pcm_buf, hw->samples); |
622 | trig = PCM_ENABLE_OUTPUT; |
623 | if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { |
624 | oss_logerr ( |
625 | errno, |
626 | "SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n" |
627 | ); |
628 | return -1; |
629 | } |
630 | } |
631 | break; |
632 | |
633 | case VOICE_DISABLE: |
634 | if (hw->poll_mode) { |
635 | qemu_set_fd_handler (oss->fd, NULL, NULL, NULL); |
636 | hw->poll_mode = 0; |
637 | } |
638 | |
639 | if (!oss->mmapped) { |
640 | return 0; |
641 | } |
642 | |
643 | ldebug ("disabling voice\n" ); |
644 | trig = 0; |
645 | if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { |
646 | oss_logerr (errno, "SNDCTL_DSP_SETTRIGGER 0 failed\n" ); |
647 | return -1; |
648 | } |
649 | break; |
650 | } |
651 | return 0; |
652 | } |
653 | |
654 | static int oss_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) |
655 | { |
656 | OSSVoiceIn *oss = (OSSVoiceIn *) hw; |
657 | struct oss_params req, obt; |
658 | int endianness; |
659 | int err; |
660 | int fd; |
661 | AudioFormat effective_fmt; |
662 | struct audsettings obt_as; |
663 | Audiodev *dev = drv_opaque; |
664 | |
665 | oss->fd = -1; |
666 | |
667 | req.fmt = aud_to_ossfmt (as->fmt, as->endianness); |
668 | req.freq = as->freq; |
669 | req.nchannels = as->nchannels; |
670 | if (oss_open(1, &req, as, &obt, &fd, dev)) { |
671 | return -1; |
672 | } |
673 | |
674 | err = oss_to_audfmt (obt.fmt, &effective_fmt, &endianness); |
675 | if (err) { |
676 | oss_anal_close (&fd); |
677 | return -1; |
678 | } |
679 | |
680 | obt_as.freq = obt.freq; |
681 | obt_as.nchannels = obt.nchannels; |
682 | obt_as.fmt = effective_fmt; |
683 | obt_as.endianness = endianness; |
684 | |
685 | audio_pcm_init_info (&hw->info, &obt_as); |
686 | oss->nfrags = obt.nfrags; |
687 | oss->fragsize = obt.fragsize; |
688 | |
689 | if (obt.nfrags * obt.fragsize & hw->info.align) { |
690 | dolog ("warning: Misaligned ADC buffer, size %d, alignment %d\n" , |
691 | obt.nfrags * obt.fragsize, hw->info.align + 1); |
692 | } |
693 | |
694 | hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift; |
695 | oss->pcm_buf = audio_calloc(__func__, hw->samples, 1 << hw->info.shift); |
696 | if (!oss->pcm_buf) { |
697 | dolog("Could not allocate ADC buffer (%zu samples, each %d bytes)\n" , |
698 | hw->samples, 1 << hw->info.shift); |
699 | oss_anal_close (&fd); |
700 | return -1; |
701 | } |
702 | |
703 | oss->fd = fd; |
704 | oss->dev = dev; |
705 | return 0; |
706 | } |
707 | |
708 | static void oss_fini_in (HWVoiceIn *hw) |
709 | { |
710 | OSSVoiceIn *oss = (OSSVoiceIn *) hw; |
711 | |
712 | oss_anal_close (&oss->fd); |
713 | |
714 | g_free(oss->pcm_buf); |
715 | oss->pcm_buf = NULL; |
716 | } |
717 | |
718 | static size_t oss_run_in(HWVoiceIn *hw) |
719 | { |
720 | OSSVoiceIn *oss = (OSSVoiceIn *) hw; |
721 | int hwshift = hw->info.shift; |
722 | int i; |
723 | size_t live = audio_pcm_hw_get_live_in (hw); |
724 | size_t dead = hw->samples - live; |
725 | size_t read_samples = 0; |
726 | struct { |
727 | size_t add; |
728 | size_t len; |
729 | } bufs[2] = { |
730 | { .add = hw->wpos, .len = 0 }, |
731 | { .add = 0, .len = 0 } |
732 | }; |
733 | |
734 | if (!dead) { |
735 | return 0; |
736 | } |
737 | |
738 | if (hw->wpos + dead > hw->samples) { |
739 | bufs[0].len = (hw->samples - hw->wpos) << hwshift; |
740 | bufs[1].len = (dead - (hw->samples - hw->wpos)) << hwshift; |
741 | } |
742 | else { |
743 | bufs[0].len = dead << hwshift; |
744 | } |
745 | |
746 | for (i = 0; i < 2; ++i) { |
747 | ssize_t nread; |
748 | |
749 | if (bufs[i].len) { |
750 | void *p = advance (oss->pcm_buf, bufs[i].add << hwshift); |
751 | nread = read (oss->fd, p, bufs[i].len); |
752 | |
753 | if (nread > 0) { |
754 | if (nread & hw->info.align) { |
755 | dolog("warning: Misaligned read %zd (requested %zu), " |
756 | "alignment %d\n" , nread, bufs[i].add << hwshift, |
757 | hw->info.align + 1); |
758 | } |
759 | read_samples += nread >> hwshift; |
760 | hw->conv (hw->conv_buf + bufs[i].add, p, nread >> hwshift); |
761 | } |
762 | |
763 | if (bufs[i].len - nread) { |
764 | if (nread == -1) { |
765 | switch (errno) { |
766 | case EINTR: |
767 | case EAGAIN: |
768 | break; |
769 | default: |
770 | oss_logerr( |
771 | errno, |
772 | "Failed to read %zu bytes of audio (to %p)\n" , |
773 | bufs[i].len, p |
774 | ); |
775 | break; |
776 | } |
777 | } |
778 | break; |
779 | } |
780 | } |
781 | } |
782 | |
783 | hw->wpos = (hw->wpos + read_samples) % hw->samples; |
784 | return read_samples; |
785 | } |
786 | |
787 | static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...) |
788 | { |
789 | OSSVoiceIn *oss = (OSSVoiceIn *) hw; |
790 | AudiodevOssPerDirectionOptions *opdo = oss->dev->u.oss.out; |
791 | |
792 | switch (cmd) { |
793 | case VOICE_ENABLE: |
794 | { |
795 | bool poll_mode = opdo->try_poll; |
796 | |
797 | if (poll_mode) { |
798 | oss_poll_in (hw); |
799 | poll_mode = 0; |
800 | } |
801 | hw->poll_mode = poll_mode; |
802 | } |
803 | break; |
804 | |
805 | case VOICE_DISABLE: |
806 | if (hw->poll_mode) { |
807 | hw->poll_mode = 0; |
808 | qemu_set_fd_handler (oss->fd, NULL, NULL, NULL); |
809 | } |
810 | break; |
811 | } |
812 | return 0; |
813 | } |
814 | |
815 | static void oss_init_per_direction(AudiodevOssPerDirectionOptions *opdo) |
816 | { |
817 | if (!opdo->has_try_poll) { |
818 | opdo->try_poll = true; |
819 | opdo->has_try_poll = true; |
820 | } |
821 | } |
822 | |
823 | static void *oss_audio_init(Audiodev *dev) |
824 | { |
825 | AudiodevOssOptions *oopts; |
826 | assert(dev->driver == AUDIODEV_DRIVER_OSS); |
827 | |
828 | oopts = &dev->u.oss; |
829 | oss_init_per_direction(oopts->in); |
830 | oss_init_per_direction(oopts->out); |
831 | |
832 | if (access(oopts->in->has_dev ? oopts->in->dev : "/dev/dsp" , |
833 | R_OK | W_OK) < 0 || |
834 | access(oopts->out->has_dev ? oopts->out->dev : "/dev/dsp" , |
835 | R_OK | W_OK) < 0) { |
836 | return NULL; |
837 | } |
838 | return dev; |
839 | } |
840 | |
841 | static void oss_audio_fini (void *opaque) |
842 | { |
843 | } |
844 | |
845 | static struct audio_pcm_ops oss_pcm_ops = { |
846 | .init_out = oss_init_out, |
847 | .fini_out = oss_fini_out, |
848 | .run_out = oss_run_out, |
849 | .ctl_out = oss_ctl_out, |
850 | |
851 | .init_in = oss_init_in, |
852 | .fini_in = oss_fini_in, |
853 | .run_in = oss_run_in, |
854 | .ctl_in = oss_ctl_in |
855 | }; |
856 | |
857 | static struct audio_driver oss_audio_driver = { |
858 | .name = "oss" , |
859 | .descr = "OSS http://www.opensound.com" , |
860 | .init = oss_audio_init, |
861 | .fini = oss_audio_fini, |
862 | .pcm_ops = &oss_pcm_ops, |
863 | .can_be_default = 1, |
864 | .max_voices_out = INT_MAX, |
865 | .max_voices_in = INT_MAX, |
866 | .voice_size_out = sizeof (OSSVoiceOut), |
867 | .voice_size_in = sizeof (OSSVoiceIn) |
868 | }; |
869 | |
870 | static void register_audio_oss(void) |
871 | { |
872 | audio_driver_register(&oss_audio_driver); |
873 | } |
874 | type_init(register_audio_oss); |
875 | |