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