|
1 /* |
|
2 * QEMU ESD audio driver |
|
3 * |
|
4 * Copyright (c) 2006 Frederick Reeve (brushed up by 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 <esd.h> |
|
25 #include "qemu-common.h" |
|
26 #include "audio.h" |
|
27 #include <signal.h> |
|
28 |
|
29 #define AUDIO_CAP "esd" |
|
30 #include "audio_int.h" |
|
31 #include "audio_pt_int.h" |
|
32 |
|
33 typedef struct { |
|
34 HWVoiceOut hw; |
|
35 int done; |
|
36 int live; |
|
37 int decr; |
|
38 int rpos; |
|
39 void *pcm_buf; |
|
40 int fd; |
|
41 struct audio_pt pt; |
|
42 } ESDVoiceOut; |
|
43 |
|
44 typedef struct { |
|
45 HWVoiceIn hw; |
|
46 int done; |
|
47 int dead; |
|
48 int incr; |
|
49 int wpos; |
|
50 void *pcm_buf; |
|
51 int fd; |
|
52 struct audio_pt pt; |
|
53 } ESDVoiceIn; |
|
54 |
|
55 static struct { |
|
56 int samples; |
|
57 int divisor; |
|
58 char *dac_host; |
|
59 char *adc_host; |
|
60 } conf = { |
|
61 1024, |
|
62 2, |
|
63 NULL, |
|
64 NULL |
|
65 }; |
|
66 |
|
67 static void GCC_FMT_ATTR (2, 3) qesd_logerr (int err, const char *fmt, ...) |
|
68 { |
|
69 va_list ap; |
|
70 |
|
71 va_start (ap, fmt); |
|
72 AUD_vlog (AUDIO_CAP, fmt, ap); |
|
73 va_end (ap); |
|
74 |
|
75 AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err)); |
|
76 } |
|
77 |
|
78 /* playback */ |
|
79 static void *qesd_thread_out (void *arg) |
|
80 { |
|
81 ESDVoiceOut *esd = arg; |
|
82 HWVoiceOut *hw = &esd->hw; |
|
83 int threshold; |
|
84 |
|
85 threshold = conf.divisor ? hw->samples / conf.divisor : 0; |
|
86 |
|
87 if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { |
|
88 return NULL; |
|
89 } |
|
90 |
|
91 for (;;) { |
|
92 int decr, to_mix, rpos; |
|
93 |
|
94 for (;;) { |
|
95 if (esd->done) { |
|
96 goto exit; |
|
97 } |
|
98 |
|
99 if (esd->live > threshold) { |
|
100 break; |
|
101 } |
|
102 |
|
103 if (audio_pt_wait (&esd->pt, AUDIO_FUNC)) { |
|
104 goto exit; |
|
105 } |
|
106 } |
|
107 |
|
108 decr = to_mix = esd->live; |
|
109 rpos = hw->rpos; |
|
110 |
|
111 if (audio_pt_unlock (&esd->pt, AUDIO_FUNC)) { |
|
112 return NULL; |
|
113 } |
|
114 |
|
115 while (to_mix) { |
|
116 ssize_t written; |
|
117 int chunk = audio_MIN (to_mix, hw->samples - rpos); |
|
118 struct st_sample *src = hw->mix_buf + rpos; |
|
119 |
|
120 hw->clip (esd->pcm_buf, src, chunk); |
|
121 |
|
122 again: |
|
123 written = write (esd->fd, esd->pcm_buf, chunk << hw->info.shift); |
|
124 if (written == -1) { |
|
125 if (errno == EINTR || errno == EAGAIN) { |
|
126 goto again; |
|
127 } |
|
128 qesd_logerr (errno, "write failed\n"); |
|
129 return NULL; |
|
130 } |
|
131 |
|
132 if (written != chunk << hw->info.shift) { |
|
133 int wsamples = written >> hw->info.shift; |
|
134 int wbytes = wsamples << hw->info.shift; |
|
135 if (wbytes != written) { |
|
136 dolog ("warning: Misaligned write %d (requested %d), " |
|
137 "alignment %d\n", |
|
138 wbytes, written, hw->info.align + 1); |
|
139 } |
|
140 to_mix -= wsamples; |
|
141 rpos = (rpos + wsamples) % hw->samples; |
|
142 break; |
|
143 } |
|
144 |
|
145 rpos = (rpos + chunk) % hw->samples; |
|
146 to_mix -= chunk; |
|
147 } |
|
148 |
|
149 if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { |
|
150 return NULL; |
|
151 } |
|
152 |
|
153 esd->rpos = rpos; |
|
154 esd->live -= decr; |
|
155 esd->decr += decr; |
|
156 } |
|
157 |
|
158 exit: |
|
159 audio_pt_unlock (&esd->pt, AUDIO_FUNC); |
|
160 return NULL; |
|
161 } |
|
162 |
|
163 static int qesd_run_out (HWVoiceOut *hw) |
|
164 { |
|
165 int live, decr; |
|
166 ESDVoiceOut *esd = (ESDVoiceOut *) hw; |
|
167 |
|
168 if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { |
|
169 return 0; |
|
170 } |
|
171 |
|
172 live = audio_pcm_hw_get_live_out (hw); |
|
173 decr = audio_MIN (live, esd->decr); |
|
174 esd->decr -= decr; |
|
175 esd->live = live - decr; |
|
176 hw->rpos = esd->rpos; |
|
177 if (esd->live > 0) { |
|
178 audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC); |
|
179 } |
|
180 else { |
|
181 audio_pt_unlock (&esd->pt, AUDIO_FUNC); |
|
182 } |
|
183 return decr; |
|
184 } |
|
185 |
|
186 static int qesd_write (SWVoiceOut *sw, void *buf, int len) |
|
187 { |
|
188 return audio_pcm_sw_write (sw, buf, len); |
|
189 } |
|
190 |
|
191 static int qesd_init_out (HWVoiceOut *hw, struct audsettings *as) |
|
192 { |
|
193 ESDVoiceOut *esd = (ESDVoiceOut *) hw; |
|
194 struct audsettings obt_as = *as; |
|
195 int esdfmt = ESD_STREAM | ESD_PLAY; |
|
196 int err; |
|
197 sigset_t set, old_set; |
|
198 |
|
199 sigfillset (&set); |
|
200 |
|
201 esdfmt |= (as->nchannels == 2) ? ESD_STEREO : ESD_MONO; |
|
202 switch (as->fmt) { |
|
203 case AUD_FMT_S8: |
|
204 case AUD_FMT_U8: |
|
205 esdfmt |= ESD_BITS8; |
|
206 obt_as.fmt = AUD_FMT_U8; |
|
207 break; |
|
208 |
|
209 case AUD_FMT_S32: |
|
210 case AUD_FMT_U32: |
|
211 dolog ("Will use 16 instead of 32 bit samples\n"); |
|
212 |
|
213 case AUD_FMT_S16: |
|
214 case AUD_FMT_U16: |
|
215 deffmt: |
|
216 esdfmt |= ESD_BITS16; |
|
217 obt_as.fmt = AUD_FMT_S16; |
|
218 break; |
|
219 |
|
220 default: |
|
221 dolog ("Internal logic error: Bad audio format %d\n", as->fmt); |
|
222 goto deffmt; |
|
223 |
|
224 } |
|
225 obt_as.endianness = AUDIO_HOST_ENDIANNESS; |
|
226 |
|
227 audio_pcm_init_info (&hw->info, &obt_as); |
|
228 |
|
229 hw->samples = conf.samples; |
|
230 esd->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); |
|
231 if (!esd->pcm_buf) { |
|
232 dolog ("Could not allocate buffer (%d bytes)\n", |
|
233 hw->samples << hw->info.shift); |
|
234 return -1; |
|
235 } |
|
236 |
|
237 esd->fd = -1; |
|
238 err = pthread_sigmask (SIG_BLOCK, &set, &old_set); |
|
239 if (err) { |
|
240 qesd_logerr (err, "pthread_sigmask failed\n"); |
|
241 goto fail1; |
|
242 } |
|
243 |
|
244 esd->fd = esd_play_stream (esdfmt, as->freq, conf.dac_host, NULL); |
|
245 if (esd->fd < 0) { |
|
246 qesd_logerr (errno, "esd_play_stream failed\n"); |
|
247 goto fail2; |
|
248 } |
|
249 |
|
250 if (audio_pt_init (&esd->pt, qesd_thread_out, esd, AUDIO_CAP, AUDIO_FUNC)) { |
|
251 goto fail3; |
|
252 } |
|
253 |
|
254 err = pthread_sigmask (SIG_SETMASK, &old_set, NULL); |
|
255 if (err) { |
|
256 qesd_logerr (err, "pthread_sigmask(restore) failed\n"); |
|
257 } |
|
258 |
|
259 return 0; |
|
260 |
|
261 fail3: |
|
262 if (close (esd->fd)) { |
|
263 qesd_logerr (errno, "%s: close on esd socket(%d) failed\n", |
|
264 AUDIO_FUNC, esd->fd); |
|
265 } |
|
266 esd->fd = -1; |
|
267 |
|
268 fail2: |
|
269 err = pthread_sigmask (SIG_SETMASK, &old_set, NULL); |
|
270 if (err) { |
|
271 qesd_logerr (err, "pthread_sigmask(restore) failed\n"); |
|
272 } |
|
273 |
|
274 fail1: |
|
275 qemu_free (esd->pcm_buf); |
|
276 esd->pcm_buf = NULL; |
|
277 return -1; |
|
278 } |
|
279 |
|
280 static void qesd_fini_out (HWVoiceOut *hw) |
|
281 { |
|
282 void *ret; |
|
283 ESDVoiceOut *esd = (ESDVoiceOut *) hw; |
|
284 |
|
285 audio_pt_lock (&esd->pt, AUDIO_FUNC); |
|
286 esd->done = 1; |
|
287 audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC); |
|
288 audio_pt_join (&esd->pt, &ret, AUDIO_FUNC); |
|
289 |
|
290 if (esd->fd >= 0) { |
|
291 if (close (esd->fd)) { |
|
292 qesd_logerr (errno, "failed to close esd socket\n"); |
|
293 } |
|
294 esd->fd = -1; |
|
295 } |
|
296 |
|
297 audio_pt_fini (&esd->pt, AUDIO_FUNC); |
|
298 |
|
299 qemu_free (esd->pcm_buf); |
|
300 esd->pcm_buf = NULL; |
|
301 } |
|
302 |
|
303 static int qesd_ctl_out (HWVoiceOut *hw, int cmd, ...) |
|
304 { |
|
305 (void) hw; |
|
306 (void) cmd; |
|
307 return 0; |
|
308 } |
|
309 |
|
310 /* capture */ |
|
311 static void *qesd_thread_in (void *arg) |
|
312 { |
|
313 ESDVoiceIn *esd = arg; |
|
314 HWVoiceIn *hw = &esd->hw; |
|
315 int threshold; |
|
316 |
|
317 threshold = conf.divisor ? hw->samples / conf.divisor : 0; |
|
318 |
|
319 if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { |
|
320 return NULL; |
|
321 } |
|
322 |
|
323 for (;;) { |
|
324 int incr, to_grab, wpos; |
|
325 |
|
326 for (;;) { |
|
327 if (esd->done) { |
|
328 goto exit; |
|
329 } |
|
330 |
|
331 if (esd->dead > threshold) { |
|
332 break; |
|
333 } |
|
334 |
|
335 if (audio_pt_wait (&esd->pt, AUDIO_FUNC)) { |
|
336 goto exit; |
|
337 } |
|
338 } |
|
339 |
|
340 incr = to_grab = esd->dead; |
|
341 wpos = hw->wpos; |
|
342 |
|
343 if (audio_pt_unlock (&esd->pt, AUDIO_FUNC)) { |
|
344 return NULL; |
|
345 } |
|
346 |
|
347 while (to_grab) { |
|
348 ssize_t nread; |
|
349 int chunk = audio_MIN (to_grab, hw->samples - wpos); |
|
350 void *buf = advance (esd->pcm_buf, wpos); |
|
351 |
|
352 again: |
|
353 nread = read (esd->fd, buf, chunk << hw->info.shift); |
|
354 if (nread == -1) { |
|
355 if (errno == EINTR || errno == EAGAIN) { |
|
356 goto again; |
|
357 } |
|
358 qesd_logerr (errno, "read failed\n"); |
|
359 return NULL; |
|
360 } |
|
361 |
|
362 if (nread != chunk << hw->info.shift) { |
|
363 int rsamples = nread >> hw->info.shift; |
|
364 int rbytes = rsamples << hw->info.shift; |
|
365 if (rbytes != nread) { |
|
366 dolog ("warning: Misaligned write %d (requested %d), " |
|
367 "alignment %d\n", |
|
368 rbytes, nread, hw->info.align + 1); |
|
369 } |
|
370 to_grab -= rsamples; |
|
371 wpos = (wpos + rsamples) % hw->samples; |
|
372 break; |
|
373 } |
|
374 |
|
375 hw->conv (hw->conv_buf + wpos, buf, nread >> hw->info.shift, |
|
376 &nominal_volume); |
|
377 wpos = (wpos + chunk) % hw->samples; |
|
378 to_grab -= chunk; |
|
379 } |
|
380 |
|
381 if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { |
|
382 return NULL; |
|
383 } |
|
384 |
|
385 esd->wpos = wpos; |
|
386 esd->dead -= incr; |
|
387 esd->incr += incr; |
|
388 } |
|
389 |
|
390 exit: |
|
391 audio_pt_unlock (&esd->pt, AUDIO_FUNC); |
|
392 return NULL; |
|
393 } |
|
394 |
|
395 static int qesd_run_in (HWVoiceIn *hw) |
|
396 { |
|
397 int live, incr, dead; |
|
398 ESDVoiceIn *esd = (ESDVoiceIn *) hw; |
|
399 |
|
400 if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { |
|
401 return 0; |
|
402 } |
|
403 |
|
404 live = audio_pcm_hw_get_live_in (hw); |
|
405 dead = hw->samples - live; |
|
406 incr = audio_MIN (dead, esd->incr); |
|
407 esd->incr -= incr; |
|
408 esd->dead = dead - incr; |
|
409 hw->wpos = esd->wpos; |
|
410 if (esd->dead > 0) { |
|
411 audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC); |
|
412 } |
|
413 else { |
|
414 audio_pt_unlock (&esd->pt, AUDIO_FUNC); |
|
415 } |
|
416 return incr; |
|
417 } |
|
418 |
|
419 static int qesd_read (SWVoiceIn *sw, void *buf, int len) |
|
420 { |
|
421 return audio_pcm_sw_read (sw, buf, len); |
|
422 } |
|
423 |
|
424 static int qesd_init_in (HWVoiceIn *hw, struct audsettings *as) |
|
425 { |
|
426 ESDVoiceIn *esd = (ESDVoiceIn *) hw; |
|
427 struct audsettings obt_as = *as; |
|
428 int esdfmt = ESD_STREAM | ESD_RECORD; |
|
429 int err; |
|
430 sigset_t set, old_set; |
|
431 |
|
432 sigfillset (&set); |
|
433 |
|
434 esdfmt |= (as->nchannels == 2) ? ESD_STEREO : ESD_MONO; |
|
435 switch (as->fmt) { |
|
436 case AUD_FMT_S8: |
|
437 case AUD_FMT_U8: |
|
438 esdfmt |= ESD_BITS8; |
|
439 obt_as.fmt = AUD_FMT_U8; |
|
440 break; |
|
441 |
|
442 case AUD_FMT_S16: |
|
443 case AUD_FMT_U16: |
|
444 esdfmt |= ESD_BITS16; |
|
445 obt_as.fmt = AUD_FMT_S16; |
|
446 break; |
|
447 |
|
448 case AUD_FMT_S32: |
|
449 case AUD_FMT_U32: |
|
450 dolog ("Will use 16 instead of 32 bit samples\n"); |
|
451 esdfmt |= ESD_BITS16; |
|
452 obt_as.fmt = AUD_FMT_S16; |
|
453 break; |
|
454 } |
|
455 obt_as.endianness = AUDIO_HOST_ENDIANNESS; |
|
456 |
|
457 audio_pcm_init_info (&hw->info, &obt_as); |
|
458 |
|
459 hw->samples = conf.samples; |
|
460 esd->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); |
|
461 if (!esd->pcm_buf) { |
|
462 dolog ("Could not allocate buffer (%d bytes)\n", |
|
463 hw->samples << hw->info.shift); |
|
464 return -1; |
|
465 } |
|
466 |
|
467 esd->fd = -1; |
|
468 |
|
469 err = pthread_sigmask (SIG_BLOCK, &set, &old_set); |
|
470 if (err) { |
|
471 qesd_logerr (err, "pthread_sigmask failed\n"); |
|
472 goto fail1; |
|
473 } |
|
474 |
|
475 esd->fd = esd_record_stream (esdfmt, as->freq, conf.adc_host, NULL); |
|
476 if (esd->fd < 0) { |
|
477 qesd_logerr (errno, "esd_record_stream failed\n"); |
|
478 goto fail2; |
|
479 } |
|
480 |
|
481 if (audio_pt_init (&esd->pt, qesd_thread_in, esd, AUDIO_CAP, AUDIO_FUNC)) { |
|
482 goto fail3; |
|
483 } |
|
484 |
|
485 err = pthread_sigmask (SIG_SETMASK, &old_set, NULL); |
|
486 if (err) { |
|
487 qesd_logerr (err, "pthread_sigmask(restore) failed\n"); |
|
488 } |
|
489 |
|
490 return 0; |
|
491 |
|
492 fail3: |
|
493 if (close (esd->fd)) { |
|
494 qesd_logerr (errno, "%s: close on esd socket(%d) failed\n", |
|
495 AUDIO_FUNC, esd->fd); |
|
496 } |
|
497 esd->fd = -1; |
|
498 |
|
499 fail2: |
|
500 err = pthread_sigmask (SIG_SETMASK, &old_set, NULL); |
|
501 if (err) { |
|
502 qesd_logerr (err, "pthread_sigmask(restore) failed\n"); |
|
503 } |
|
504 |
|
505 fail1: |
|
506 qemu_free (esd->pcm_buf); |
|
507 esd->pcm_buf = NULL; |
|
508 return -1; |
|
509 } |
|
510 |
|
511 static void qesd_fini_in (HWVoiceIn *hw) |
|
512 { |
|
513 void *ret; |
|
514 ESDVoiceIn *esd = (ESDVoiceIn *) hw; |
|
515 |
|
516 audio_pt_lock (&esd->pt, AUDIO_FUNC); |
|
517 esd->done = 1; |
|
518 audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC); |
|
519 audio_pt_join (&esd->pt, &ret, AUDIO_FUNC); |
|
520 |
|
521 if (esd->fd >= 0) { |
|
522 if (close (esd->fd)) { |
|
523 qesd_logerr (errno, "failed to close esd socket\n"); |
|
524 } |
|
525 esd->fd = -1; |
|
526 } |
|
527 |
|
528 audio_pt_fini (&esd->pt, AUDIO_FUNC); |
|
529 |
|
530 qemu_free (esd->pcm_buf); |
|
531 esd->pcm_buf = NULL; |
|
532 } |
|
533 |
|
534 static int qesd_ctl_in (HWVoiceIn *hw, int cmd, ...) |
|
535 { |
|
536 (void) hw; |
|
537 (void) cmd; |
|
538 return 0; |
|
539 } |
|
540 |
|
541 /* common */ |
|
542 static void *qesd_audio_init (void) |
|
543 { |
|
544 return &conf; |
|
545 } |
|
546 |
|
547 static void qesd_audio_fini (void *opaque) |
|
548 { |
|
549 (void) opaque; |
|
550 ldebug ("esd_fini"); |
|
551 } |
|
552 |
|
553 struct audio_option qesd_options[] = { |
|
554 {"SAMPLES", AUD_OPT_INT, &conf.samples, |
|
555 "buffer size in samples", NULL, 0}, |
|
556 |
|
557 {"DIVISOR", AUD_OPT_INT, &conf.divisor, |
|
558 "threshold divisor", NULL, 0}, |
|
559 |
|
560 {"DAC_HOST", AUD_OPT_STR, &conf.dac_host, |
|
561 "playback host", NULL, 0}, |
|
562 |
|
563 {"ADC_HOST", AUD_OPT_STR, &conf.adc_host, |
|
564 "capture host", NULL, 0}, |
|
565 |
|
566 {NULL, 0, NULL, NULL, NULL, 0} |
|
567 }; |
|
568 |
|
569 static struct audio_pcm_ops qesd_pcm_ops = { |
|
570 qesd_init_out, |
|
571 qesd_fini_out, |
|
572 qesd_run_out, |
|
573 qesd_write, |
|
574 qesd_ctl_out, |
|
575 |
|
576 qesd_init_in, |
|
577 qesd_fini_in, |
|
578 qesd_run_in, |
|
579 qesd_read, |
|
580 qesd_ctl_in, |
|
581 }; |
|
582 |
|
583 struct audio_driver esd_audio_driver = { |
|
584 INIT_FIELD (name = ) "esd", |
|
585 INIT_FIELD (descr = ) |
|
586 "http://en.wikipedia.org/wiki/Esound", |
|
587 INIT_FIELD (options = ) qesd_options, |
|
588 INIT_FIELD (init = ) qesd_audio_init, |
|
589 INIT_FIELD (fini = ) qesd_audio_fini, |
|
590 INIT_FIELD (pcm_ops = ) &qesd_pcm_ops, |
|
591 INIT_FIELD (can_be_default = ) 0, |
|
592 INIT_FIELD (max_voices_out = ) INT_MAX, |
|
593 INIT_FIELD (max_voices_in = ) INT_MAX, |
|
594 INIT_FIELD (voice_size_out = ) sizeof (ESDVoiceOut), |
|
595 INIT_FIELD (voice_size_in = ) sizeof (ESDVoiceIn) |
|
596 }; |