xref: /illumos-kvm-cmd/audio/paaudio.c (revision 68396ea9)
1 /* public domain */
2 #include "qemu-common.h"
3 #include "audio.h"
4 
5 #include <pulse/simple.h>
6 #include <pulse/error.h>
7 
8 #define AUDIO_CAP "pulseaudio"
9 #include "audio_int.h"
10 #include "audio_pt_int.h"
11 
12 typedef struct {
13     HWVoiceOut hw;
14     int done;
15     int live;
16     int decr;
17     int rpos;
18     pa_simple *s;
19     void *pcm_buf;
20     struct audio_pt pt;
21 } PAVoiceOut;
22 
23 typedef struct {
24     HWVoiceIn hw;
25     int done;
26     int dead;
27     int incr;
28     int wpos;
29     pa_simple *s;
30     void *pcm_buf;
31     struct audio_pt pt;
32 } PAVoiceIn;
33 
34 static struct {
35     int samples;
36     char *server;
37     char *sink;
38     char *source;
39 } conf = {
40     .samples = 4096,
41 };
42 
qpa_logerr(int err,const char * fmt,...)43 static void GCC_FMT_ATTR (2, 3) qpa_logerr (int err, const char *fmt, ...)
44 {
45     va_list ap;
46 
47     va_start (ap, fmt);
48     AUD_vlog (AUDIO_CAP, fmt, ap);
49     va_end (ap);
50 
51     AUD_log (AUDIO_CAP, "Reason: %s\n", pa_strerror (err));
52 }
53 
qpa_thread_out(void * arg)54 static void *qpa_thread_out (void *arg)
55 {
56     PAVoiceOut *pa = arg;
57     HWVoiceOut *hw = &pa->hw;
58 
59     if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) {
60         return NULL;
61     }
62 
63     for (;;) {
64         int decr, to_mix, rpos;
65 
66         for (;;) {
67             if (pa->done) {
68                 goto exit;
69             }
70 
71             if (pa->live > 0) {
72                 break;
73             }
74 
75             if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) {
76                 goto exit;
77             }
78         }
79 
80         decr = to_mix = audio_MIN (pa->live, conf.samples >> 2);
81         rpos = pa->rpos;
82 
83         if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) {
84             return NULL;
85         }
86 
87         while (to_mix) {
88             int error;
89             int chunk = audio_MIN (to_mix, hw->samples - rpos);
90             struct st_sample *src = hw->mix_buf + rpos;
91 
92             hw->clip (pa->pcm_buf, src, chunk);
93 
94             if (pa_simple_write (pa->s, pa->pcm_buf,
95                                  chunk << hw->info.shift, &error) < 0) {
96                 qpa_logerr (error, "pa_simple_write failed\n");
97                 return NULL;
98             }
99 
100             rpos = (rpos + chunk) % hw->samples;
101             to_mix -= chunk;
102         }
103 
104         if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) {
105             return NULL;
106         }
107 
108         pa->rpos = rpos;
109         pa->live -= decr;
110         pa->decr += decr;
111     }
112 
113  exit:
114     audio_pt_unlock (&pa->pt, AUDIO_FUNC);
115     return NULL;
116 }
117 
qpa_run_out(HWVoiceOut * hw,int live)118 static int qpa_run_out (HWVoiceOut *hw, int live)
119 {
120     int decr;
121     PAVoiceOut *pa = (PAVoiceOut *) hw;
122 
123     if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) {
124         return 0;
125     }
126 
127     decr = audio_MIN (live, pa->decr);
128     pa->decr -= decr;
129     pa->live = live - decr;
130     hw->rpos = pa->rpos;
131     if (pa->live > 0) {
132         audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC);
133     }
134     else {
135         audio_pt_unlock (&pa->pt, AUDIO_FUNC);
136     }
137     return decr;
138 }
139 
qpa_write(SWVoiceOut * sw,void * buf,int len)140 static int qpa_write (SWVoiceOut *sw, void *buf, int len)
141 {
142     return audio_pcm_sw_write (sw, buf, len);
143 }
144 
145 /* capture */
qpa_thread_in(void * arg)146 static void *qpa_thread_in (void *arg)
147 {
148     PAVoiceIn *pa = arg;
149     HWVoiceIn *hw = &pa->hw;
150 
151     if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) {
152         return NULL;
153     }
154 
155     for (;;) {
156         int incr, to_grab, wpos;
157 
158         for (;;) {
159             if (pa->done) {
160                 goto exit;
161             }
162 
163             if (pa->dead > 0) {
164                 break;
165             }
166 
167             if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) {
168                 goto exit;
169             }
170         }
171 
172         incr = to_grab = audio_MIN (pa->dead, conf.samples >> 2);
173         wpos = pa->wpos;
174 
175         if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) {
176             return NULL;
177         }
178 
179         while (to_grab) {
180             int error;
181             int chunk = audio_MIN (to_grab, hw->samples - wpos);
182             void *buf = advance (pa->pcm_buf, wpos);
183 
184             if (pa_simple_read (pa->s, buf,
185                                 chunk << hw->info.shift, &error) < 0) {
186                 qpa_logerr (error, "pa_simple_read failed\n");
187                 return NULL;
188             }
189 
190             hw->conv (hw->conv_buf + wpos, buf, chunk);
191             wpos = (wpos + chunk) % hw->samples;
192             to_grab -= chunk;
193         }
194 
195         if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) {
196             return NULL;
197         }
198 
199         pa->wpos = wpos;
200         pa->dead -= incr;
201         pa->incr += incr;
202     }
203 
204  exit:
205     audio_pt_unlock (&pa->pt, AUDIO_FUNC);
206     return NULL;
207 }
208 
qpa_run_in(HWVoiceIn * hw)209 static int qpa_run_in (HWVoiceIn *hw)
210 {
211     int live, incr, dead;
212     PAVoiceIn *pa = (PAVoiceIn *) hw;
213 
214     if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) {
215         return 0;
216     }
217 
218     live = audio_pcm_hw_get_live_in (hw);
219     dead = hw->samples - live;
220     incr = audio_MIN (dead, pa->incr);
221     pa->incr -= incr;
222     pa->dead = dead - incr;
223     hw->wpos = pa->wpos;
224     if (pa->dead > 0) {
225         audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC);
226     }
227     else {
228         audio_pt_unlock (&pa->pt, AUDIO_FUNC);
229     }
230     return incr;
231 }
232 
qpa_read(SWVoiceIn * sw,void * buf,int len)233 static int qpa_read (SWVoiceIn *sw, void *buf, int len)
234 {
235     return audio_pcm_sw_read (sw, buf, len);
236 }
237 
audfmt_to_pa(audfmt_e afmt,int endianness)238 static pa_sample_format_t audfmt_to_pa (audfmt_e afmt, int endianness)
239 {
240     int format;
241 
242     switch (afmt) {
243     case AUD_FMT_S8:
244     case AUD_FMT_U8:
245         format = PA_SAMPLE_U8;
246         break;
247     case AUD_FMT_S16:
248     case AUD_FMT_U16:
249         format = endianness ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE;
250         break;
251     case AUD_FMT_S32:
252     case AUD_FMT_U32:
253         format = endianness ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE;
254         break;
255     default:
256         dolog ("Internal logic error: Bad audio format %d\n", afmt);
257         format = PA_SAMPLE_U8;
258         break;
259     }
260     return format;
261 }
262 
pa_to_audfmt(pa_sample_format_t fmt,int * endianness)263 static audfmt_e pa_to_audfmt (pa_sample_format_t fmt, int *endianness)
264 {
265     switch (fmt) {
266     case PA_SAMPLE_U8:
267         return AUD_FMT_U8;
268     case PA_SAMPLE_S16BE:
269         *endianness = 1;
270         return AUD_FMT_S16;
271     case PA_SAMPLE_S16LE:
272         *endianness = 0;
273         return AUD_FMT_S16;
274     case PA_SAMPLE_S32BE:
275         *endianness = 1;
276         return AUD_FMT_S32;
277     case PA_SAMPLE_S32LE:
278         *endianness = 0;
279         return AUD_FMT_S32;
280     default:
281         dolog ("Internal logic error: Bad pa_sample_format %d\n", fmt);
282         return AUD_FMT_U8;
283     }
284 }
285 
qpa_init_out(HWVoiceOut * hw,struct audsettings * as)286 static int qpa_init_out (HWVoiceOut *hw, struct audsettings *as)
287 {
288     int error;
289     static pa_sample_spec ss;
290     static pa_buffer_attr ba;
291     struct audsettings obt_as = *as;
292     PAVoiceOut *pa = (PAVoiceOut *) hw;
293 
294     ss.format = audfmt_to_pa (as->fmt, as->endianness);
295     ss.channels = as->nchannels;
296     ss.rate = as->freq;
297 
298     /*
299      * qemu audio tick runs at 250 Hz (by default), so processing
300      * data chunks worth 4 ms of sound should be a good fit.
301      */
302     ba.tlength = pa_usec_to_bytes (4 * 1000, &ss);
303     ba.minreq = pa_usec_to_bytes (2 * 1000, &ss);
304     ba.maxlength = -1;
305     ba.prebuf = -1;
306 
307     obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness);
308 
309     pa->s = pa_simple_new (
310         conf.server,
311         "qemu",
312         PA_STREAM_PLAYBACK,
313         conf.sink,
314         "pcm.playback",
315         &ss,
316         NULL,                   /* channel map */
317         &ba,                    /* buffering attributes */
318         &error
319         );
320     if (!pa->s) {
321         qpa_logerr (error, "pa_simple_new for playback failed\n");
322         goto fail1;
323     }
324 
325     audio_pcm_init_info (&hw->info, &obt_as);
326     hw->samples = conf.samples;
327     pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
328     pa->rpos = hw->rpos;
329     if (!pa->pcm_buf) {
330         dolog ("Could not allocate buffer (%d bytes)\n",
331                hw->samples << hw->info.shift);
332         goto fail2;
333     }
334 
335     if (audio_pt_init (&pa->pt, qpa_thread_out, hw, AUDIO_CAP, AUDIO_FUNC)) {
336         goto fail3;
337     }
338 
339     return 0;
340 
341  fail3:
342     qemu_free (pa->pcm_buf);
343     pa->pcm_buf = NULL;
344  fail2:
345     pa_simple_free (pa->s);
346     pa->s = NULL;
347  fail1:
348     return -1;
349 }
350 
qpa_init_in(HWVoiceIn * hw,struct audsettings * as)351 static int qpa_init_in (HWVoiceIn *hw, struct audsettings *as)
352 {
353     int error;
354     static pa_sample_spec ss;
355     struct audsettings obt_as = *as;
356     PAVoiceIn *pa = (PAVoiceIn *) hw;
357 
358     ss.format = audfmt_to_pa (as->fmt, as->endianness);
359     ss.channels = as->nchannels;
360     ss.rate = as->freq;
361 
362     obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness);
363 
364     pa->s = pa_simple_new (
365         conf.server,
366         "qemu",
367         PA_STREAM_RECORD,
368         conf.source,
369         "pcm.capture",
370         &ss,
371         NULL,                   /* channel map */
372         NULL,                   /* buffering attributes */
373         &error
374         );
375     if (!pa->s) {
376         qpa_logerr (error, "pa_simple_new for capture failed\n");
377         goto fail1;
378     }
379 
380     audio_pcm_init_info (&hw->info, &obt_as);
381     hw->samples = conf.samples;
382     pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
383     pa->wpos = hw->wpos;
384     if (!pa->pcm_buf) {
385         dolog ("Could not allocate buffer (%d bytes)\n",
386                hw->samples << hw->info.shift);
387         goto fail2;
388     }
389 
390     if (audio_pt_init (&pa->pt, qpa_thread_in, hw, AUDIO_CAP, AUDIO_FUNC)) {
391         goto fail3;
392     }
393 
394     return 0;
395 
396  fail3:
397     qemu_free (pa->pcm_buf);
398     pa->pcm_buf = NULL;
399  fail2:
400     pa_simple_free (pa->s);
401     pa->s = NULL;
402  fail1:
403     return -1;
404 }
405 
qpa_fini_out(HWVoiceOut * hw)406 static void qpa_fini_out (HWVoiceOut *hw)
407 {
408     void *ret;
409     PAVoiceOut *pa = (PAVoiceOut *) hw;
410 
411     audio_pt_lock (&pa->pt, AUDIO_FUNC);
412     pa->done = 1;
413     audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC);
414     audio_pt_join (&pa->pt, &ret, AUDIO_FUNC);
415 
416     if (pa->s) {
417         pa_simple_free (pa->s);
418         pa->s = NULL;
419     }
420 
421     audio_pt_fini (&pa->pt, AUDIO_FUNC);
422     qemu_free (pa->pcm_buf);
423     pa->pcm_buf = NULL;
424 }
425 
qpa_fini_in(HWVoiceIn * hw)426 static void qpa_fini_in (HWVoiceIn *hw)
427 {
428     void *ret;
429     PAVoiceIn *pa = (PAVoiceIn *) hw;
430 
431     audio_pt_lock (&pa->pt, AUDIO_FUNC);
432     pa->done = 1;
433     audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC);
434     audio_pt_join (&pa->pt, &ret, AUDIO_FUNC);
435 
436     if (pa->s) {
437         pa_simple_free (pa->s);
438         pa->s = NULL;
439     }
440 
441     audio_pt_fini (&pa->pt, AUDIO_FUNC);
442     qemu_free (pa->pcm_buf);
443     pa->pcm_buf = NULL;
444 }
445 
qpa_ctl_out(HWVoiceOut * hw,int cmd,...)446 static int qpa_ctl_out (HWVoiceOut *hw, int cmd, ...)
447 {
448     (void) hw;
449     (void) cmd;
450     return 0;
451 }
452 
qpa_ctl_in(HWVoiceIn * hw,int cmd,...)453 static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...)
454 {
455     (void) hw;
456     (void) cmd;
457     return 0;
458 }
459 
460 /* common */
qpa_audio_init(void)461 static void *qpa_audio_init (void)
462 {
463     return &conf;
464 }
465 
qpa_audio_fini(void * opaque)466 static void qpa_audio_fini (void *opaque)
467 {
468     (void) opaque;
469 }
470 
471 struct audio_option qpa_options[] = {
472     {
473         .name  = "SAMPLES",
474         .tag   = AUD_OPT_INT,
475         .valp  = &conf.samples,
476         .descr = "buffer size in samples"
477     },
478     {
479         .name  = "SERVER",
480         .tag   = AUD_OPT_STR,
481         .valp  = &conf.server,
482         .descr = "server address"
483     },
484     {
485         .name  = "SINK",
486         .tag   = AUD_OPT_STR,
487         .valp  = &conf.sink,
488         .descr = "sink device name"
489     },
490     {
491         .name  = "SOURCE",
492         .tag   = AUD_OPT_STR,
493         .valp  = &conf.source,
494         .descr = "source device name"
495     },
496     { /* End of list */ }
497 };
498 
499 static struct audio_pcm_ops qpa_pcm_ops = {
500     .init_out = qpa_init_out,
501     .fini_out = qpa_fini_out,
502     .run_out  = qpa_run_out,
503     .write    = qpa_write,
504     .ctl_out  = qpa_ctl_out,
505 
506     .init_in  = qpa_init_in,
507     .fini_in  = qpa_fini_in,
508     .run_in   = qpa_run_in,
509     .read     = qpa_read,
510     .ctl_in   = qpa_ctl_in
511 };
512 
513 struct audio_driver pa_audio_driver = {
514     .name           = "pa",
515     .descr          = "http://www.pulseaudio.org/",
516     .options        = qpa_options,
517     .init           = qpa_audio_init,
518     .fini           = qpa_audio_fini,
519     .pcm_ops        = &qpa_pcm_ops,
520     .can_be_default = 1,
521     .max_voices_out = INT_MAX,
522     .max_voices_in  = INT_MAX,
523     .voice_size_out = sizeof (PAVoiceOut),
524     .voice_size_in  = sizeof (PAVoiceIn)
525 };
526