xref: /openbmc/qemu/audio/paaudio.c (revision a76e6b8794727ae777e33856e1ccd2b410d0bde2)
1b8e59f18Smalc /* public domain */
20b8fa32fSMarkus Armbruster 
36086a565SPeter Maydell #include "qemu/osdep.h"
40b8fa32fSMarkus Armbruster #include "qemu/module.h"
53443ad4eSKővágó, Zoltán #include "qemu-common.h"
6b8e59f18Smalc #include "audio.h"
72c324b28SKővágó, Zoltán #include "qapi/opts-visitor.h"
8b8e59f18Smalc 
9ea9ebc2cSMarc-André Lureau #include <pulse/pulseaudio.h>
10b8e59f18Smalc 
11b8e59f18Smalc #define AUDIO_CAP "pulseaudio"
12b8e59f18Smalc #include "audio_int.h"
13b8e59f18Smalc 
149d34e6d8SKővágó, Zoltán typedef struct PAConnection {
159d34e6d8SKővágó, Zoltán     char *server;
169d34e6d8SKővágó, Zoltán     int refcount;
179d34e6d8SKővágó, Zoltán     QTAILQ_ENTRY(PAConnection) list;
189d34e6d8SKővágó, Zoltán 
199a644c4bSKővágó, Zoltán     pa_threaded_mainloop *mainloop;
209a644c4bSKővágó, Zoltán     pa_context *context;
219d34e6d8SKővágó, Zoltán } PAConnection;
229d34e6d8SKővágó, Zoltán 
239d34e6d8SKővágó, Zoltán static QTAILQ_HEAD(PAConnectionHead, PAConnection) pa_conns =
249d34e6d8SKővágó, Zoltán     QTAILQ_HEAD_INITIALIZER(pa_conns);
259d34e6d8SKővágó, Zoltán 
269d34e6d8SKővágó, Zoltán typedef struct {
279d34e6d8SKővágó, Zoltán     Audiodev *dev;
289d34e6d8SKővágó, Zoltán     PAConnection *conn;
299a644c4bSKővágó, Zoltán } paaudio;
309a644c4bSKővágó, Zoltán 
319a644c4bSKővágó, Zoltán typedef struct {
32b8e59f18Smalc     HWVoiceOut hw;
33ea9ebc2cSMarc-André Lureau     pa_stream *stream;
349a644c4bSKővágó, Zoltán     paaudio *g;
35b8e59f18Smalc } PAVoiceOut;
36b8e59f18Smalc 
37b8e59f18Smalc typedef struct {
38b8e59f18Smalc     HWVoiceIn hw;
39ea9ebc2cSMarc-André Lureau     pa_stream *stream;
40ea9ebc2cSMarc-André Lureau     const void *read_data;
4149ddd7e1SKővágó, Zoltán     size_t read_length;
429a644c4bSKővágó, Zoltán     paaudio *g;
43b8e59f18Smalc } PAVoiceIn;
44b8e59f18Smalc 
459d34e6d8SKővágó, Zoltán static void qpa_conn_fini(PAConnection *c);
4649dd6d0dSKővágó, Zoltán 
47b8e59f18Smalc static void GCC_FMT_ATTR (2, 3) qpa_logerr (int err, const char *fmt, ...)
48b8e59f18Smalc {
49b8e59f18Smalc     va_list ap;
50b8e59f18Smalc 
51b8e59f18Smalc     va_start (ap, fmt);
52b8e59f18Smalc     AUD_vlog (AUDIO_CAP, fmt, ap);
53b8e59f18Smalc     va_end (ap);
54b8e59f18Smalc 
55b8e59f18Smalc     AUD_log (AUDIO_CAP, "Reason: %s\n", pa_strerror (err));
56b8e59f18Smalc }
57b8e59f18Smalc 
588f473dd1SGerd Hoffmann #ifndef PA_CONTEXT_IS_GOOD
598f473dd1SGerd Hoffmann static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x)
608f473dd1SGerd Hoffmann {
618f473dd1SGerd Hoffmann     return
628f473dd1SGerd Hoffmann         x == PA_CONTEXT_CONNECTING ||
638f473dd1SGerd Hoffmann         x == PA_CONTEXT_AUTHORIZING ||
648f473dd1SGerd Hoffmann         x == PA_CONTEXT_SETTING_NAME ||
658f473dd1SGerd Hoffmann         x == PA_CONTEXT_READY;
668f473dd1SGerd Hoffmann }
678f473dd1SGerd Hoffmann #endif
688f473dd1SGerd Hoffmann 
698f473dd1SGerd Hoffmann #ifndef PA_STREAM_IS_GOOD
708f473dd1SGerd Hoffmann static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x)
718f473dd1SGerd Hoffmann {
728f473dd1SGerd Hoffmann     return
738f473dd1SGerd Hoffmann         x == PA_STREAM_CREATING ||
748f473dd1SGerd Hoffmann         x == PA_STREAM_READY;
758f473dd1SGerd Hoffmann }
768f473dd1SGerd Hoffmann #endif
778f473dd1SGerd Hoffmann 
7849ddd7e1SKővágó, Zoltán #define CHECK_SUCCESS_GOTO(c, expression, label, msg)           \
79ea9ebc2cSMarc-André Lureau     do {                                                        \
80ea9ebc2cSMarc-André Lureau         if (!(expression)) {                                    \
8149ddd7e1SKővágó, Zoltán             qpa_logerr(pa_context_errno((c)->context), msg);    \
82ea9ebc2cSMarc-André Lureau             goto label;                                         \
83ea9ebc2cSMarc-André Lureau         }                                                       \
842562755eSEric Blake     } while (0)
85ea9ebc2cSMarc-André Lureau 
8649ddd7e1SKővágó, Zoltán #define CHECK_DEAD_GOTO(c, stream, label, msg)                          \
87ea9ebc2cSMarc-André Lureau     do {                                                                \
88ea9ebc2cSMarc-André Lureau         if (!(c)->context || !PA_CONTEXT_IS_GOOD (pa_context_get_state((c)->context)) || \
89ea9ebc2cSMarc-André Lureau             !(stream) || !PA_STREAM_IS_GOOD (pa_stream_get_state ((stream)))) { \
90ea9ebc2cSMarc-André Lureau             if (((c)->context && pa_context_get_state ((c)->context) == PA_CONTEXT_FAILED) || \
91ea9ebc2cSMarc-André Lureau                 ((stream) && pa_stream_get_state ((stream)) == PA_STREAM_FAILED)) { \
9249ddd7e1SKővágó, Zoltán                 qpa_logerr(pa_context_errno((c)->context), msg);        \
93ea9ebc2cSMarc-André Lureau             } else {                                                    \
9449ddd7e1SKővágó, Zoltán                 qpa_logerr(PA_ERR_BADSTATE, msg);                       \
95ea9ebc2cSMarc-André Lureau             }                                                           \
96ea9ebc2cSMarc-André Lureau             goto label;                                                 \
97ea9ebc2cSMarc-André Lureau         }                                                               \
982562755eSEric Blake     } while (0)
99ea9ebc2cSMarc-André Lureau 
100337e8de6SKővágó, Zoltán static void *qpa_get_buffer_in(HWVoiceIn *hw, size_t *size)
101337e8de6SKővágó, Zoltán {
102337e8de6SKővágó, Zoltán     PAVoiceIn *p = (PAVoiceIn *) hw;
103337e8de6SKővágó, Zoltán     PAConnection *c = p->g->conn;
104337e8de6SKővágó, Zoltán     int r;
105337e8de6SKővágó, Zoltán 
106337e8de6SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
107337e8de6SKővágó, Zoltán 
108337e8de6SKővágó, Zoltán     CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail,
109337e8de6SKővágó, Zoltán                     "pa_threaded_mainloop_lock failed\n");
110337e8de6SKővágó, Zoltán 
111337e8de6SKővágó, Zoltán     if (!p->read_length) {
112337e8de6SKővágó, Zoltán         r = pa_stream_peek(p->stream, &p->read_data, &p->read_length);
113337e8de6SKővágó, Zoltán         CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail,
114337e8de6SKővágó, Zoltán                            "pa_stream_peek failed\n");
115337e8de6SKővágó, Zoltán     }
116337e8de6SKővágó, Zoltán 
117337e8de6SKővágó, Zoltán     *size = MIN(p->read_length, *size);
118337e8de6SKővágó, Zoltán 
119337e8de6SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
120337e8de6SKővágó, Zoltán     return (void *) p->read_data;
121337e8de6SKővágó, Zoltán 
122337e8de6SKővágó, Zoltán unlock_and_fail:
123337e8de6SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
124337e8de6SKővágó, Zoltán     *size = 0;
125337e8de6SKővágó, Zoltán     return NULL;
126337e8de6SKővágó, Zoltán }
127337e8de6SKővágó, Zoltán 
128337e8de6SKővágó, Zoltán static void qpa_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)
129337e8de6SKővágó, Zoltán {
130337e8de6SKővágó, Zoltán     PAVoiceIn *p = (PAVoiceIn *) hw;
131337e8de6SKővágó, Zoltán     PAConnection *c = p->g->conn;
132337e8de6SKővágó, Zoltán     int r;
133337e8de6SKővágó, Zoltán 
134337e8de6SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
135337e8de6SKővágó, Zoltán 
136337e8de6SKővágó, Zoltán     CHECK_DEAD_GOTO(c, p->stream, unlock,
137337e8de6SKővágó, Zoltán                     "pa_threaded_mainloop_lock failed\n");
138337e8de6SKővágó, Zoltán 
139337e8de6SKővágó, Zoltán     assert(buf == p->read_data && size <= p->read_length);
140337e8de6SKővágó, Zoltán 
141337e8de6SKővágó, Zoltán     p->read_data += size;
142337e8de6SKővágó, Zoltán     p->read_length -= size;
143337e8de6SKővágó, Zoltán 
144337e8de6SKővágó, Zoltán     if (size && !p->read_length) {
145337e8de6SKővágó, Zoltán         r = pa_stream_drop(p->stream);
146337e8de6SKővágó, Zoltán         CHECK_SUCCESS_GOTO(c, r == 0, unlock, "pa_stream_drop failed\n");
147337e8de6SKővágó, Zoltán     }
148337e8de6SKővágó, Zoltán 
149337e8de6SKővágó, Zoltán unlock:
150337e8de6SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
151337e8de6SKővágó, Zoltán }
152337e8de6SKővágó, Zoltán 
15349ddd7e1SKővágó, Zoltán static size_t qpa_read(HWVoiceIn *hw, void *data, size_t length)
154ea9ebc2cSMarc-André Lureau {
15549ddd7e1SKővágó, Zoltán     PAVoiceIn *p = (PAVoiceIn *) hw;
1569d34e6d8SKővágó, Zoltán     PAConnection *c = p->g->conn;
157acc3b63eSVolker Rümelin     size_t total = 0;
158ea9ebc2cSMarc-André Lureau 
1599d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
160ea9ebc2cSMarc-André Lureau 
16149ddd7e1SKővágó, Zoltán     CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail,
16249ddd7e1SKővágó, Zoltán                     "pa_threaded_mainloop_lock failed\n");
1637c9eb86eSVolker Rümelin     if (pa_stream_get_state(p->stream) != PA_STREAM_READY) {
1647c9eb86eSVolker Rümelin         /* wait for stream to become ready */
1657c9eb86eSVolker Rümelin         goto unlock;
1667c9eb86eSVolker Rümelin     }
167ea9ebc2cSMarc-André Lureau 
168acc3b63eSVolker Rümelin     while (total < length) {
169acc3b63eSVolker Rümelin         size_t l;
170acc3b63eSVolker Rümelin         int r;
171acc3b63eSVolker Rümelin 
17249ddd7e1SKővágó, Zoltán         if (!p->read_length) {
173ea9ebc2cSMarc-André Lureau             r = pa_stream_peek(p->stream, &p->read_data, &p->read_length);
17449ddd7e1SKővágó, Zoltán             CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail,
17549ddd7e1SKővágó, Zoltán                                "pa_stream_peek failed\n");
176acc3b63eSVolker Rümelin             if (!p->read_length) {
177acc3b63eSVolker Rümelin                 /* buffer is empty */
178acc3b63eSVolker Rümelin                 break;
179acc3b63eSVolker Rümelin             }
180ea9ebc2cSMarc-André Lureau         }
181ea9ebc2cSMarc-André Lureau 
182acc3b63eSVolker Rümelin         l = MIN(p->read_length, length - total);
183acc3b63eSVolker Rümelin         memcpy((char *)data + total, p->read_data, l);
184ea9ebc2cSMarc-André Lureau 
18549ddd7e1SKővágó, Zoltán         p->read_data += l;
186ea9ebc2cSMarc-André Lureau         p->read_length -= l;
187acc3b63eSVolker Rümelin         total += l;
188ea9ebc2cSMarc-André Lureau 
189ea9ebc2cSMarc-André Lureau         if (!p->read_length) {
190ea9ebc2cSMarc-André Lureau             r = pa_stream_drop(p->stream);
19149ddd7e1SKővágó, Zoltán             CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail,
19249ddd7e1SKővágó, Zoltán                                "pa_stream_drop failed\n");
193ea9ebc2cSMarc-André Lureau         }
194acc3b63eSVolker Rümelin     }
195ea9ebc2cSMarc-André Lureau 
1967c9eb86eSVolker Rümelin unlock:
1979d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
198acc3b63eSVolker Rümelin     return total;
199ea9ebc2cSMarc-André Lureau 
200ea9ebc2cSMarc-André Lureau unlock_and_fail:
2019d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
20249ddd7e1SKővágó, Zoltán     return 0;
203ea9ebc2cSMarc-André Lureau }
204ea9ebc2cSMarc-André Lureau 
205337e8de6SKővágó, Zoltán static void *qpa_get_buffer_out(HWVoiceOut *hw, size_t *size)
206337e8de6SKővágó, Zoltán {
207337e8de6SKővágó, Zoltán     PAVoiceOut *p = (PAVoiceOut *) hw;
208337e8de6SKővágó, Zoltán     PAConnection *c = p->g->conn;
209337e8de6SKővágó, Zoltán     void *ret;
210337e8de6SKővágó, Zoltán     int r;
211337e8de6SKővágó, Zoltán 
212337e8de6SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
213337e8de6SKővágó, Zoltán 
214337e8de6SKővágó, Zoltán     CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail,
215337e8de6SKővágó, Zoltán                     "pa_threaded_mainloop_lock failed\n");
216337e8de6SKővágó, Zoltán 
217337e8de6SKővágó, Zoltán     *size = -1;
218337e8de6SKővágó, Zoltán     r = pa_stream_begin_write(p->stream, &ret, size);
219337e8de6SKővágó, Zoltán     CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail,
220337e8de6SKővágó, Zoltán                        "pa_stream_begin_write failed\n");
221337e8de6SKővágó, Zoltán 
222337e8de6SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
223337e8de6SKővágó, Zoltán     return ret;
224337e8de6SKővágó, Zoltán 
225337e8de6SKővágó, Zoltán unlock_and_fail:
226337e8de6SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
227337e8de6SKővágó, Zoltán     *size = 0;
228337e8de6SKővágó, Zoltán     return NULL;
229337e8de6SKővágó, Zoltán }
230337e8de6SKővágó, Zoltán 
23149ddd7e1SKővágó, Zoltán static size_t qpa_write(HWVoiceOut *hw, void *data, size_t length)
232ea9ebc2cSMarc-André Lureau {
23349ddd7e1SKővágó, Zoltán     PAVoiceOut *p = (PAVoiceOut *) hw;
2349d34e6d8SKővágó, Zoltán     PAConnection *c = p->g->conn;
235ea9ebc2cSMarc-André Lureau     size_t l;
236ea9ebc2cSMarc-André Lureau     int r;
237ea9ebc2cSMarc-André Lureau 
23849ddd7e1SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
239ea9ebc2cSMarc-André Lureau 
24049ddd7e1SKővágó, Zoltán     CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail,
24149ddd7e1SKővágó, Zoltán                     "pa_threaded_mainloop_lock failed\n");
24249ddd7e1SKővágó, Zoltán 
24349ddd7e1SKővágó, Zoltán     l = pa_stream_writable_size(p->stream);
24449ddd7e1SKővágó, Zoltán 
24549ddd7e1SKővágó, Zoltán     CHECK_SUCCESS_GOTO(c, l != (size_t) -1, unlock_and_fail,
24649ddd7e1SKővágó, Zoltán                        "pa_stream_writable_size failed\n");
247ea9ebc2cSMarc-André Lureau 
248ea9ebc2cSMarc-André Lureau     if (l > length) {
249ea9ebc2cSMarc-André Lureau         l = length;
250ea9ebc2cSMarc-André Lureau     }
251ea9ebc2cSMarc-André Lureau 
252ea9ebc2cSMarc-André Lureau     r = pa_stream_write(p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE);
25349ddd7e1SKővágó, Zoltán     CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail, "pa_stream_write failed\n");
254ea9ebc2cSMarc-André Lureau 
2559d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
25649ddd7e1SKővágó, Zoltán     return l;
257ea9ebc2cSMarc-André Lureau 
258ea9ebc2cSMarc-André Lureau unlock_and_fail:
2599d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
260b8e59f18Smalc     return 0;
261b8e59f18Smalc }
262b8e59f18Smalc 
26385bc5852SKővágó, Zoltán static pa_sample_format_t audfmt_to_pa (AudioFormat afmt, int endianness)
264b8e59f18Smalc {
265b8e59f18Smalc     int format;
266b8e59f18Smalc 
267b8e59f18Smalc     switch (afmt) {
26885bc5852SKővágó, Zoltán     case AUDIO_FORMAT_S8:
26985bc5852SKővágó, Zoltán     case AUDIO_FORMAT_U8:
270b8e59f18Smalc         format = PA_SAMPLE_U8;
271b8e59f18Smalc         break;
27285bc5852SKővágó, Zoltán     case AUDIO_FORMAT_S16:
27385bc5852SKővágó, Zoltán     case AUDIO_FORMAT_U16:
274b8e59f18Smalc         format = endianness ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE;
275b8e59f18Smalc         break;
27685bc5852SKővágó, Zoltán     case AUDIO_FORMAT_S32:
27785bc5852SKővágó, Zoltán     case AUDIO_FORMAT_U32:
278b8e59f18Smalc         format = endianness ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE;
279b8e59f18Smalc         break;
280b8e59f18Smalc     default:
281b8e59f18Smalc         dolog ("Internal logic error: Bad audio format %d\n", afmt);
282b8e59f18Smalc         format = PA_SAMPLE_U8;
283b8e59f18Smalc         break;
284b8e59f18Smalc     }
285b8e59f18Smalc     return format;
286b8e59f18Smalc }
287b8e59f18Smalc 
28885bc5852SKővágó, Zoltán static AudioFormat pa_to_audfmt (pa_sample_format_t fmt, int *endianness)
289b8e59f18Smalc {
290b8e59f18Smalc     switch (fmt) {
291b8e59f18Smalc     case PA_SAMPLE_U8:
29285bc5852SKővágó, Zoltán         return AUDIO_FORMAT_U8;
293b8e59f18Smalc     case PA_SAMPLE_S16BE:
294b8e59f18Smalc         *endianness = 1;
29585bc5852SKővágó, Zoltán         return AUDIO_FORMAT_S16;
296b8e59f18Smalc     case PA_SAMPLE_S16LE:
297b8e59f18Smalc         *endianness = 0;
29885bc5852SKővágó, Zoltán         return AUDIO_FORMAT_S16;
299b8e59f18Smalc     case PA_SAMPLE_S32BE:
300b8e59f18Smalc         *endianness = 1;
30185bc5852SKővágó, Zoltán         return AUDIO_FORMAT_S32;
302b8e59f18Smalc     case PA_SAMPLE_S32LE:
303b8e59f18Smalc         *endianness = 0;
30485bc5852SKővágó, Zoltán         return AUDIO_FORMAT_S32;
305b8e59f18Smalc     default:
306b8e59f18Smalc         dolog ("Internal logic error: Bad pa_sample_format %d\n", fmt);
30785bc5852SKővágó, Zoltán         return AUDIO_FORMAT_U8;
308b8e59f18Smalc     }
309b8e59f18Smalc }
310b8e59f18Smalc 
311ea9ebc2cSMarc-André Lureau static void context_state_cb (pa_context *c, void *userdata)
312ea9ebc2cSMarc-André Lureau {
3139d34e6d8SKővágó, Zoltán     PAConnection *conn = userdata;
314ea9ebc2cSMarc-André Lureau 
315ea9ebc2cSMarc-André Lureau     switch (pa_context_get_state(c)) {
316ea9ebc2cSMarc-André Lureau     case PA_CONTEXT_READY:
317ea9ebc2cSMarc-André Lureau     case PA_CONTEXT_TERMINATED:
318ea9ebc2cSMarc-André Lureau     case PA_CONTEXT_FAILED:
3199d34e6d8SKővágó, Zoltán         pa_threaded_mainloop_signal(conn->mainloop, 0);
320ea9ebc2cSMarc-André Lureau         break;
321ea9ebc2cSMarc-André Lureau 
322ea9ebc2cSMarc-André Lureau     case PA_CONTEXT_UNCONNECTED:
323ea9ebc2cSMarc-André Lureau     case PA_CONTEXT_CONNECTING:
324ea9ebc2cSMarc-André Lureau     case PA_CONTEXT_AUTHORIZING:
325ea9ebc2cSMarc-André Lureau     case PA_CONTEXT_SETTING_NAME:
326ea9ebc2cSMarc-André Lureau         break;
327ea9ebc2cSMarc-André Lureau     }
328ea9ebc2cSMarc-André Lureau }
329ea9ebc2cSMarc-André Lureau 
330ea9ebc2cSMarc-André Lureau static void stream_state_cb (pa_stream *s, void * userdata)
331ea9ebc2cSMarc-André Lureau {
3329d34e6d8SKővágó, Zoltán     PAConnection *c = userdata;
333ea9ebc2cSMarc-André Lureau 
334ea9ebc2cSMarc-André Lureau     switch (pa_stream_get_state (s)) {
335ea9ebc2cSMarc-André Lureau 
336ea9ebc2cSMarc-André Lureau     case PA_STREAM_READY:
337ea9ebc2cSMarc-André Lureau     case PA_STREAM_FAILED:
338ea9ebc2cSMarc-André Lureau     case PA_STREAM_TERMINATED:
3399d34e6d8SKővágó, Zoltán         pa_threaded_mainloop_signal(c->mainloop, 0);
340ea9ebc2cSMarc-André Lureau         break;
341ea9ebc2cSMarc-André Lureau 
342ea9ebc2cSMarc-André Lureau     case PA_STREAM_UNCONNECTED:
343ea9ebc2cSMarc-André Lureau     case PA_STREAM_CREATING:
344ea9ebc2cSMarc-André Lureau         break;
345ea9ebc2cSMarc-André Lureau     }
346ea9ebc2cSMarc-André Lureau }
347ea9ebc2cSMarc-André Lureau 
348ea9ebc2cSMarc-André Lureau static pa_stream *qpa_simple_new (
3499d34e6d8SKővágó, Zoltán         PAConnection *c,
350ea9ebc2cSMarc-André Lureau         const char *name,
351ea9ebc2cSMarc-André Lureau         pa_stream_direction_t dir,
352ea9ebc2cSMarc-André Lureau         const char *dev,
353ea9ebc2cSMarc-André Lureau         const pa_sample_spec *ss,
354ea9ebc2cSMarc-André Lureau         const pa_buffer_attr *attr,
355ea9ebc2cSMarc-André Lureau         int *rerror)
356ea9ebc2cSMarc-André Lureau {
357ea9ebc2cSMarc-André Lureau     int r;
3580cf13e36SKővágó, Zoltán     pa_stream *stream = NULL;
3599d34e6d8SKővágó, Zoltán     pa_stream_flags_t flags;
3600cf13e36SKővágó, Zoltán     pa_channel_map map;
361ea9ebc2cSMarc-André Lureau 
3629d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
363ea9ebc2cSMarc-André Lureau 
3640cf13e36SKővágó, Zoltán     pa_channel_map_init(&map);
3650cf13e36SKővágó, Zoltán     map.channels = ss->channels;
3660cf13e36SKővágó, Zoltán 
3670cf13e36SKővágó, Zoltán     /*
3680cf13e36SKővágó, Zoltán      * TODO: This currently expects the only frontend supporting more than 2
3690cf13e36SKővágó, Zoltán      * channels is the usb-audio.  We will need some means to set channel
3700cf13e36SKővágó, Zoltán      * order when a new frontend gains multi-channel support.
3710cf13e36SKővágó, Zoltán      */
3720cf13e36SKővágó, Zoltán     switch (ss->channels) {
3730cf13e36SKővágó, Zoltán     case 1:
3740cf13e36SKővágó, Zoltán         map.map[0] = PA_CHANNEL_POSITION_MONO;
3750cf13e36SKővágó, Zoltán         break;
3760cf13e36SKővágó, Zoltán 
3770cf13e36SKővágó, Zoltán     case 2:
3780cf13e36SKővágó, Zoltán         map.map[0] = PA_CHANNEL_POSITION_LEFT;
3790cf13e36SKővágó, Zoltán         map.map[1] = PA_CHANNEL_POSITION_RIGHT;
3800cf13e36SKővágó, Zoltán         break;
3810cf13e36SKővágó, Zoltán 
3820cf13e36SKővágó, Zoltán     case 6:
3830cf13e36SKővágó, Zoltán         map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
3840cf13e36SKővágó, Zoltán         map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
3850cf13e36SKővágó, Zoltán         map.map[2] = PA_CHANNEL_POSITION_CENTER;
3860cf13e36SKővágó, Zoltán         map.map[3] = PA_CHANNEL_POSITION_LFE;
3870cf13e36SKővágó, Zoltán         map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT;
3880cf13e36SKővágó, Zoltán         map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT;
3890cf13e36SKővágó, Zoltán         break;
3900cf13e36SKővágó, Zoltán 
3910cf13e36SKővágó, Zoltán     case 8:
3920cf13e36SKővágó, Zoltán         map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
3930cf13e36SKővágó, Zoltán         map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
3940cf13e36SKővágó, Zoltán         map.map[2] = PA_CHANNEL_POSITION_CENTER;
3950cf13e36SKővágó, Zoltán         map.map[3] = PA_CHANNEL_POSITION_LFE;
3960cf13e36SKővágó, Zoltán         map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT;
3970cf13e36SKővágó, Zoltán         map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT;
3980cf13e36SKővágó, Zoltán         map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT;
3990cf13e36SKővágó, Zoltán         map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT;
40056089565SPaolo Bonzini         break;
4010cf13e36SKővágó, Zoltán 
4020cf13e36SKővágó, Zoltán     default:
4030cf13e36SKővágó, Zoltán         dolog("Internal error: unsupported channel count %d\n", ss->channels);
4040cf13e36SKővágó, Zoltán         goto fail;
4050cf13e36SKővágó, Zoltán     }
4060cf13e36SKővágó, Zoltán 
4070cf13e36SKővágó, Zoltán     stream = pa_stream_new(c->context, name, ss, &map);
408ea9ebc2cSMarc-André Lureau     if (!stream) {
409ea9ebc2cSMarc-André Lureau         goto fail;
410ea9ebc2cSMarc-André Lureau     }
411ea9ebc2cSMarc-André Lureau 
4129d34e6d8SKővágó, Zoltán     pa_stream_set_state_callback(stream, stream_state_cb, c);
4139d34e6d8SKővágó, Zoltán 
4149d34e6d8SKővágó, Zoltán     flags =
4159d34e6d8SKővágó, Zoltán         PA_STREAM_INTERPOLATE_TIMING
41610d5e750SKővágó, Zoltán         | PA_STREAM_AUTO_TIMING_UPDATE
41710d5e750SKővágó, Zoltán         | PA_STREAM_EARLY_REQUESTS;
418ea9ebc2cSMarc-André Lureau 
4198a435f74SKővágó, Zoltán     if (dev) {
4208a435f74SKővágó, Zoltán         /* don't move the stream if the user specified a sink/source */
4218a435f74SKővágó, Zoltán         flags |= PA_STREAM_DONT_MOVE;
4228a435f74SKővágó, Zoltán     }
4238a435f74SKővágó, Zoltán 
424ea9ebc2cSMarc-André Lureau     if (dir == PA_STREAM_PLAYBACK) {
4259d34e6d8SKővágó, Zoltán         r = pa_stream_connect_playback(stream, dev, attr, flags, NULL, NULL);
426ea9ebc2cSMarc-André Lureau     } else {
4279d34e6d8SKővágó, Zoltán         r = pa_stream_connect_record(stream, dev, attr, flags);
428ea9ebc2cSMarc-André Lureau     }
429ea9ebc2cSMarc-André Lureau 
430ea9ebc2cSMarc-André Lureau     if (r < 0) {
431ea9ebc2cSMarc-André Lureau       goto fail;
432ea9ebc2cSMarc-André Lureau     }
433ea9ebc2cSMarc-André Lureau 
4349d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
435ea9ebc2cSMarc-André Lureau 
436ea9ebc2cSMarc-André Lureau     return stream;
437ea9ebc2cSMarc-André Lureau 
438ea9ebc2cSMarc-André Lureau fail:
4399d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
440ea9ebc2cSMarc-André Lureau 
441ea9ebc2cSMarc-André Lureau     if (stream) {
442ea9ebc2cSMarc-André Lureau         pa_stream_unref (stream);
443ea9ebc2cSMarc-André Lureau     }
444ea9ebc2cSMarc-André Lureau 
4459d34e6d8SKővágó, Zoltán     *rerror = pa_context_errno(c->context);
446ea9ebc2cSMarc-André Lureau 
447ea9ebc2cSMarc-André Lureau     return NULL;
448ea9ebc2cSMarc-André Lureau }
449ea9ebc2cSMarc-André Lureau 
4505706db1dSKővágó, Zoltán static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as,
4515706db1dSKővágó, Zoltán                         void *drv_opaque)
452b8e59f18Smalc {
453b8e59f18Smalc     int error;
4549a644c4bSKővágó, Zoltán     pa_sample_spec ss;
4559a644c4bSKővágó, Zoltán     pa_buffer_attr ba;
4561ea879e5Smalc     struct audsettings obt_as = *as;
457b8e59f18Smalc     PAVoiceOut *pa = (PAVoiceOut *) hw;
4589a644c4bSKővágó, Zoltán     paaudio *g = pa->g = drv_opaque;
4592c324b28SKővágó, Zoltán     AudiodevPaOptions *popts = &g->dev->u.pa;
4602c324b28SKővágó, Zoltán     AudiodevPaPerDirectionOptions *ppdo = popts->out;
4619d34e6d8SKővágó, Zoltán     PAConnection *c = g->conn;
462b8e59f18Smalc 
463b8e59f18Smalc     ss.format = audfmt_to_pa (as->fmt, as->endianness);
464b8e59f18Smalc     ss.channels = as->nchannels;
465b8e59f18Smalc     ss.rate = as->freq;
466b8e59f18Smalc 
467f6142777SMartin Schrodt     ba.tlength = pa_usec_to_bytes(ppdo->latency, &ss);
468f6142777SMartin Schrodt     ba.minreq = -1;
469e6d16fa4SGerd Hoffmann     ba.maxlength = -1;
470e6d16fa4SGerd Hoffmann     ba.prebuf = -1;
471e6d16fa4SGerd Hoffmann 
472b8e59f18Smalc     obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness);
473b8e59f18Smalc 
474ea9ebc2cSMarc-André Lureau     pa->stream = qpa_simple_new (
4759d34e6d8SKővágó, Zoltán         c,
476f47dffe8SKővágó, Zoltán         ppdo->has_stream_name ? ppdo->stream_name : g->dev->id,
477b8e59f18Smalc         PA_STREAM_PLAYBACK,
4782c324b28SKővágó, Zoltán         ppdo->has_name ? ppdo->name : NULL,
479b8e59f18Smalc         &ss,
480e6d16fa4SGerd Hoffmann         &ba,                    /* buffering attributes */
481b8e59f18Smalc         &error
482b8e59f18Smalc         );
483ea9ebc2cSMarc-André Lureau     if (!pa->stream) {
484b8e59f18Smalc         qpa_logerr (error, "pa_simple_new for playback failed\n");
485b8e59f18Smalc         goto fail1;
486b8e59f18Smalc     }
487b8e59f18Smalc 
488b8e59f18Smalc     audio_pcm_init_info (&hw->info, &obt_as);
489*a76e6b87SVolker Rümelin     hw->samples = audio_buffer_samples(
490baea032eSMartin Schrodt         qapi_AudiodevPaPerDirectionOptions_base(ppdo),
491baea032eSMartin Schrodt         &obt_as, ppdo->buffer_length);
492b8e59f18Smalc 
493b8e59f18Smalc     return 0;
494b8e59f18Smalc 
495b8e59f18Smalc  fail1:
496b8e59f18Smalc     return -1;
497b8e59f18Smalc }
498b8e59f18Smalc 
4995706db1dSKővágó, Zoltán static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
500b8e59f18Smalc {
501b8e59f18Smalc     int error;
5029a644c4bSKővágó, Zoltán     pa_sample_spec ss;
503ade10301SMartin Schrodt     pa_buffer_attr ba;
5041ea879e5Smalc     struct audsettings obt_as = *as;
505b8e59f18Smalc     PAVoiceIn *pa = (PAVoiceIn *) hw;
5069a644c4bSKővágó, Zoltán     paaudio *g = pa->g = drv_opaque;
5072c324b28SKővágó, Zoltán     AudiodevPaOptions *popts = &g->dev->u.pa;
5082c324b28SKővágó, Zoltán     AudiodevPaPerDirectionOptions *ppdo = popts->in;
5099d34e6d8SKővágó, Zoltán     PAConnection *c = g->conn;
510b8e59f18Smalc 
511b8e59f18Smalc     ss.format = audfmt_to_pa (as->fmt, as->endianness);
512b8e59f18Smalc     ss.channels = as->nchannels;
513b8e59f18Smalc     ss.rate = as->freq;
514b8e59f18Smalc 
515ade10301SMartin Schrodt     ba.fragsize = pa_usec_to_bytes(ppdo->latency, &ss);
51658c15e52SMartin Schrodt     ba.maxlength = pa_usec_to_bytes(ppdo->latency * 2, &ss);
517ade10301SMartin Schrodt     ba.minreq = -1;
518ade10301SMartin Schrodt     ba.prebuf = -1;
519ade10301SMartin Schrodt 
520b8e59f18Smalc     obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness);
521b8e59f18Smalc 
522ea9ebc2cSMarc-André Lureau     pa->stream = qpa_simple_new (
5239d34e6d8SKővágó, Zoltán         c,
524f47dffe8SKővágó, Zoltán         ppdo->has_stream_name ? ppdo->stream_name : g->dev->id,
525b8e59f18Smalc         PA_STREAM_RECORD,
5262c324b28SKővágó, Zoltán         ppdo->has_name ? ppdo->name : NULL,
527b8e59f18Smalc         &ss,
528ade10301SMartin Schrodt         &ba,                    /* buffering attributes */
529b8e59f18Smalc         &error
530b8e59f18Smalc         );
531ea9ebc2cSMarc-André Lureau     if (!pa->stream) {
532b8e59f18Smalc         qpa_logerr (error, "pa_simple_new for capture failed\n");
533b8e59f18Smalc         goto fail1;
534b8e59f18Smalc     }
535b8e59f18Smalc 
536b8e59f18Smalc     audio_pcm_init_info (&hw->info, &obt_as);
537*a76e6b87SVolker Rümelin     hw->samples = audio_buffer_samples(
538baea032eSMartin Schrodt         qapi_AudiodevPaPerDirectionOptions_base(ppdo),
539baea032eSMartin Schrodt         &obt_as, ppdo->buffer_length);
540b8e59f18Smalc 
541b8e59f18Smalc     return 0;
542b8e59f18Smalc 
543b8e59f18Smalc  fail1:
544b8e59f18Smalc     return -1;
545b8e59f18Smalc }
546b8e59f18Smalc 
5478692bf7dSKővágó, Zoltán static void qpa_simple_disconnect(PAConnection *c, pa_stream *stream)
5488692bf7dSKővágó, Zoltán {
5498692bf7dSKővágó, Zoltán     int err;
5508692bf7dSKővágó, Zoltán 
5518692bf7dSKővágó, Zoltán     /*
5528692bf7dSKővágó, Zoltán      * wait until actually connects. workaround pa bug #247
5538692bf7dSKővágó, Zoltán      * https://gitlab.freedesktop.org/pulseaudio/pulseaudio/issues/247
5548692bf7dSKővágó, Zoltán      */
5558692bf7dSKővágó, Zoltán     while (pa_stream_get_state(stream) == PA_STREAM_CREATING) {
5568692bf7dSKővágó, Zoltán         pa_threaded_mainloop_wait(c->mainloop);
5578692bf7dSKővágó, Zoltán     }
5588692bf7dSKővágó, Zoltán 
5598692bf7dSKővágó, Zoltán     err = pa_stream_disconnect(stream);
5608692bf7dSKővágó, Zoltán     if (err != 0) {
5618692bf7dSKővágó, Zoltán         dolog("Failed to disconnect! err=%d\n", err);
5628692bf7dSKővágó, Zoltán     }
5638692bf7dSKővágó, Zoltán     pa_stream_unref(stream);
5648692bf7dSKővágó, Zoltán }
5658692bf7dSKővágó, Zoltán 
566b8e59f18Smalc static void qpa_fini_out (HWVoiceOut *hw)
567b8e59f18Smalc {
568b8e59f18Smalc     PAVoiceOut *pa = (PAVoiceOut *) hw;
569b8e59f18Smalc 
570ea9ebc2cSMarc-André Lureau     if (pa->stream) {
5714db3e634SVolker Rümelin         PAConnection *c = pa->g->conn;
5724db3e634SVolker Rümelin 
5734db3e634SVolker Rümelin         pa_threaded_mainloop_lock(c->mainloop);
5744db3e634SVolker Rümelin         qpa_simple_disconnect(c, pa->stream);
575ea9ebc2cSMarc-André Lureau         pa->stream = NULL;
5764db3e634SVolker Rümelin         pa_threaded_mainloop_unlock(c->mainloop);
577b8e59f18Smalc     }
578b8e59f18Smalc }
579b8e59f18Smalc 
580b8e59f18Smalc static void qpa_fini_in (HWVoiceIn *hw)
581b8e59f18Smalc {
582b8e59f18Smalc     PAVoiceIn *pa = (PAVoiceIn *) hw;
583b8e59f18Smalc 
584ea9ebc2cSMarc-André Lureau     if (pa->stream) {
5854db3e634SVolker Rümelin         PAConnection *c = pa->g->conn;
5864db3e634SVolker Rümelin 
5874db3e634SVolker Rümelin         pa_threaded_mainloop_lock(c->mainloop);
5884db3e634SVolker Rümelin         if (pa->read_length) {
5894db3e634SVolker Rümelin             int r = pa_stream_drop(pa->stream);
5904db3e634SVolker Rümelin             if (r) {
5914db3e634SVolker Rümelin                 qpa_logerr(pa_context_errno(c->context),
5924db3e634SVolker Rümelin                            "pa_stream_drop failed\n");
5934db3e634SVolker Rümelin             }
5944db3e634SVolker Rümelin             pa->read_length = 0;
5954db3e634SVolker Rümelin         }
5964db3e634SVolker Rümelin         qpa_simple_disconnect(c, pa->stream);
597ea9ebc2cSMarc-André Lureau         pa->stream = NULL;
5984db3e634SVolker Rümelin         pa_threaded_mainloop_unlock(c->mainloop);
599b8e59f18Smalc     }
600b8e59f18Smalc }
601b8e59f18Smalc 
602cecc1e79SKővágó, Zoltán static void qpa_volume_out(HWVoiceOut *hw, Volume *vol)
603b8e59f18Smalc {
6046e7a7f3dSMarc-André Lureau     PAVoiceOut *pa = (PAVoiceOut *) hw;
6056e7a7f3dSMarc-André Lureau     pa_operation *op;
6066e7a7f3dSMarc-André Lureau     pa_cvolume v;
6079d34e6d8SKővágó, Zoltán     PAConnection *c = pa->g->conn;
608cecc1e79SKővágó, Zoltán     int i;
6096e7a7f3dSMarc-André Lureau 
6108f473dd1SGerd Hoffmann #ifdef PA_CHECK_VERSION    /* macro is present in 0.9.16+ */
6118f473dd1SGerd Hoffmann     pa_cvolume_init (&v);  /* function is present in 0.9.13+ */
6128f473dd1SGerd Hoffmann #endif
6136e7a7f3dSMarc-André Lureau 
614cecc1e79SKővágó, Zoltán     v.channels = vol->channels;
615cecc1e79SKővágó, Zoltán     for (i = 0; i < vol->channels; ++i) {
616cecc1e79SKővágó, Zoltán         v.values[i] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->vol[i]) / 255;
617cecc1e79SKővágó, Zoltán     }
6186e7a7f3dSMarc-André Lureau 
6199d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
6206e7a7f3dSMarc-André Lureau 
6219d34e6d8SKővágó, Zoltán     op = pa_context_set_sink_input_volume(c->context,
6226e7a7f3dSMarc-André Lureau                                           pa_stream_get_index(pa->stream),
6236e7a7f3dSMarc-André Lureau                                           &v, NULL, NULL);
6249d34e6d8SKővágó, Zoltán     if (!op) {
6259d34e6d8SKővágó, Zoltán         qpa_logerr(pa_context_errno(c->context),
6266e7a7f3dSMarc-André Lureau                    "set_sink_input_volume() failed\n");
6279d34e6d8SKővágó, Zoltán     } else {
6286e7a7f3dSMarc-André Lureau         pa_operation_unref(op);
6299d34e6d8SKővágó, Zoltán     }
6306e7a7f3dSMarc-André Lureau 
6319d34e6d8SKővágó, Zoltán     op = pa_context_set_sink_input_mute(c->context,
6326e7a7f3dSMarc-André Lureau                                         pa_stream_get_index(pa->stream),
633571a8c52SKővágó, Zoltán                                         vol->mute, NULL, NULL);
6346e7a7f3dSMarc-André Lureau     if (!op) {
6359d34e6d8SKővágó, Zoltán         qpa_logerr(pa_context_errno(c->context),
6366e7a7f3dSMarc-André Lureau                    "set_sink_input_mute() failed\n");
6376e7a7f3dSMarc-André Lureau     } else {
6386e7a7f3dSMarc-André Lureau         pa_operation_unref(op);
6396e7a7f3dSMarc-André Lureau     }
6406e7a7f3dSMarc-André Lureau 
6419d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
6426e7a7f3dSMarc-André Lureau }
643b8e59f18Smalc 
644cecc1e79SKővágó, Zoltán static void qpa_volume_in(HWVoiceIn *hw, Volume *vol)
645b8e59f18Smalc {
6466e7a7f3dSMarc-André Lureau     PAVoiceIn *pa = (PAVoiceIn *) hw;
6476e7a7f3dSMarc-André Lureau     pa_operation *op;
6486e7a7f3dSMarc-André Lureau     pa_cvolume v;
6499d34e6d8SKővágó, Zoltán     PAConnection *c = pa->g->conn;
650cecc1e79SKővágó, Zoltán     int i;
6516e7a7f3dSMarc-André Lureau 
6528f473dd1SGerd Hoffmann #ifdef PA_CHECK_VERSION
6536e7a7f3dSMarc-André Lureau     pa_cvolume_init (&v);
6548f473dd1SGerd Hoffmann #endif
6556e7a7f3dSMarc-André Lureau 
656cecc1e79SKővágó, Zoltán     v.channels = vol->channels;
657cecc1e79SKővágó, Zoltán     for (i = 0; i < vol->channels; ++i) {
658cecc1e79SKővágó, Zoltán         v.values[i] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->vol[i]) / 255;
659cecc1e79SKővágó, Zoltán     }
6606e7a7f3dSMarc-André Lureau 
6619d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
6626e7a7f3dSMarc-André Lureau 
6639d34e6d8SKővágó, Zoltán     op = pa_context_set_source_output_volume(c->context,
664e58ff62dSPeter Krempa         pa_stream_get_index(pa->stream),
6656e7a7f3dSMarc-André Lureau         &v, NULL, NULL);
6666e7a7f3dSMarc-André Lureau     if (!op) {
6679d34e6d8SKővágó, Zoltán         qpa_logerr(pa_context_errno(c->context),
668e58ff62dSPeter Krempa                    "set_source_output_volume() failed\n");
6696e7a7f3dSMarc-André Lureau     } else {
6706e7a7f3dSMarc-André Lureau         pa_operation_unref(op);
6716e7a7f3dSMarc-André Lureau     }
6726e7a7f3dSMarc-André Lureau 
6739d34e6d8SKővágó, Zoltán     op = pa_context_set_source_output_mute(c->context,
6746e7a7f3dSMarc-André Lureau         pa_stream_get_index(pa->stream),
675571a8c52SKővágó, Zoltán         vol->mute, NULL, NULL);
6766e7a7f3dSMarc-André Lureau     if (!op) {
6779d34e6d8SKővágó, Zoltán         qpa_logerr(pa_context_errno(c->context),
678e58ff62dSPeter Krempa                    "set_source_output_mute() failed\n");
6796e7a7f3dSMarc-André Lureau     } else {
6806e7a7f3dSMarc-André Lureau         pa_operation_unref(op);
6816e7a7f3dSMarc-André Lureau     }
6826e7a7f3dSMarc-André Lureau 
6839d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
6846e7a7f3dSMarc-André Lureau }
685b8e59f18Smalc 
686baea032eSMartin Schrodt static int qpa_validate_per_direction_opts(Audiodev *dev,
687baea032eSMartin Schrodt                                            AudiodevPaPerDirectionOptions *pdo)
688baea032eSMartin Schrodt {
689baea032eSMartin Schrodt     if (!pdo->has_buffer_length) {
690baea032eSMartin Schrodt         pdo->has_buffer_length = true;
691baea032eSMartin Schrodt         pdo->buffer_length = 46440;
692baea032eSMartin Schrodt     }
693f6142777SMartin Schrodt     if (!pdo->has_latency) {
694f6142777SMartin Schrodt         pdo->has_latency = true;
695f6142777SMartin Schrodt         pdo->latency = 15000;
696f6142777SMartin Schrodt     }
697baea032eSMartin Schrodt     return 1;
698baea032eSMartin Schrodt }
699baea032eSMartin Schrodt 
7009d34e6d8SKővágó, Zoltán /* common */
7019d34e6d8SKővágó, Zoltán static void *qpa_conn_init(const char *server)
7029d34e6d8SKővágó, Zoltán {
7033443ad4eSKővágó, Zoltán     const char *vm_name;
7049d34e6d8SKővágó, Zoltán     PAConnection *c = g_malloc0(sizeof(PAConnection));
7059d34e6d8SKővágó, Zoltán     QTAILQ_INSERT_TAIL(&pa_conns, c, list);
7069d34e6d8SKővágó, Zoltán 
7079d34e6d8SKővágó, Zoltán     c->mainloop = pa_threaded_mainloop_new();
7089d34e6d8SKővágó, Zoltán     if (!c->mainloop) {
7099d34e6d8SKővágó, Zoltán         goto fail;
7109d34e6d8SKővágó, Zoltán     }
7119d34e6d8SKővágó, Zoltán 
7123443ad4eSKővágó, Zoltán     vm_name = qemu_get_vm_name();
7139d34e6d8SKővágó, Zoltán     c->context = pa_context_new(pa_threaded_mainloop_get_api(c->mainloop),
7143443ad4eSKővágó, Zoltán                                 vm_name ? vm_name : "qemu");
7159d34e6d8SKővágó, Zoltán     if (!c->context) {
7169d34e6d8SKővágó, Zoltán         goto fail;
7179d34e6d8SKővágó, Zoltán     }
7189d34e6d8SKővágó, Zoltán 
7199d34e6d8SKővágó, Zoltán     pa_context_set_state_callback(c->context, context_state_cb, c);
7209d34e6d8SKővágó, Zoltán 
7219d34e6d8SKővágó, Zoltán     if (pa_context_connect(c->context, server, 0, NULL) < 0) {
7229d34e6d8SKővágó, Zoltán         qpa_logerr(pa_context_errno(c->context),
7239d34e6d8SKővágó, Zoltán                    "pa_context_connect() failed\n");
7249d34e6d8SKővágó, Zoltán         goto fail;
7259d34e6d8SKővágó, Zoltán     }
7269d34e6d8SKővágó, Zoltán 
7279d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
7289d34e6d8SKővágó, Zoltán 
7299d34e6d8SKővágó, Zoltán     if (pa_threaded_mainloop_start(c->mainloop) < 0) {
7309d34e6d8SKővágó, Zoltán         goto unlock_and_fail;
7319d34e6d8SKővágó, Zoltán     }
7329d34e6d8SKővágó, Zoltán 
7339d34e6d8SKővágó, Zoltán     for (;;) {
7349d34e6d8SKővágó, Zoltán         pa_context_state_t state;
7359d34e6d8SKővágó, Zoltán 
7369d34e6d8SKővágó, Zoltán         state = pa_context_get_state(c->context);
7379d34e6d8SKővágó, Zoltán 
7389d34e6d8SKővágó, Zoltán         if (state == PA_CONTEXT_READY) {
7399d34e6d8SKővágó, Zoltán             break;
7409d34e6d8SKővágó, Zoltán         }
7419d34e6d8SKővágó, Zoltán 
7429d34e6d8SKővágó, Zoltán         if (!PA_CONTEXT_IS_GOOD(state)) {
7439d34e6d8SKővágó, Zoltán             qpa_logerr(pa_context_errno(c->context),
7449d34e6d8SKővágó, Zoltán                        "Wrong context state\n");
7459d34e6d8SKővágó, Zoltán             goto unlock_and_fail;
7469d34e6d8SKővágó, Zoltán         }
7479d34e6d8SKővágó, Zoltán 
7489d34e6d8SKővágó, Zoltán         /* Wait until the context is ready */
7499d34e6d8SKővágó, Zoltán         pa_threaded_mainloop_wait(c->mainloop);
7509d34e6d8SKővágó, Zoltán     }
7519d34e6d8SKővágó, Zoltán 
7529d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
7539d34e6d8SKővágó, Zoltán     return c;
7549d34e6d8SKővágó, Zoltán 
7559d34e6d8SKővágó, Zoltán unlock_and_fail:
7569d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
7579d34e6d8SKővágó, Zoltán fail:
7589d34e6d8SKővágó, Zoltán     AUD_log (AUDIO_CAP, "Failed to initialize PA context");
7599d34e6d8SKővágó, Zoltán     qpa_conn_fini(c);
7609d34e6d8SKővágó, Zoltán     return NULL;
7619d34e6d8SKővágó, Zoltán }
7629d34e6d8SKővágó, Zoltán 
76371830221SKővágó, Zoltán static void *qpa_audio_init(Audiodev *dev)
764b8e59f18Smalc {
7652c324b28SKővágó, Zoltán     paaudio *g;
7662c324b28SKővágó, Zoltán     AudiodevPaOptions *popts = &dev->u.pa;
7672c324b28SKővágó, Zoltán     const char *server;
7689d34e6d8SKővágó, Zoltán     PAConnection *c;
7699d34e6d8SKővágó, Zoltán 
7709d34e6d8SKővágó, Zoltán     assert(dev->driver == AUDIODEV_DRIVER_PA);
7712c324b28SKővágó, Zoltán 
7722c324b28SKővágó, Zoltán     if (!popts->has_server) {
773d175505bSGerd Hoffmann         char pidfile[64];
774d175505bSGerd Hoffmann         char *runtime;
775d175505bSGerd Hoffmann         struct stat st;
776d175505bSGerd Hoffmann 
777d175505bSGerd Hoffmann         runtime = getenv("XDG_RUNTIME_DIR");
778d175505bSGerd Hoffmann         if (!runtime) {
779d175505bSGerd Hoffmann             return NULL;
780d175505bSGerd Hoffmann         }
781d175505bSGerd Hoffmann         snprintf(pidfile, sizeof(pidfile), "%s/pulse/pid", runtime);
782d175505bSGerd Hoffmann         if (stat(pidfile, &st) != 0) {
783d175505bSGerd Hoffmann             return NULL;
784d175505bSGerd Hoffmann         }
785d175505bSGerd Hoffmann     }
786d175505bSGerd Hoffmann 
787baea032eSMartin Schrodt     if (!qpa_validate_per_direction_opts(dev, popts->in)) {
7889d34e6d8SKővágó, Zoltán         return NULL;
789baea032eSMartin Schrodt     }
790baea032eSMartin Schrodt     if (!qpa_validate_per_direction_opts(dev, popts->out)) {
7919d34e6d8SKővágó, Zoltán         return NULL;
792baea032eSMartin Schrodt     }
793baea032eSMartin Schrodt 
7949d34e6d8SKővágó, Zoltán     g = g_malloc0(sizeof(paaudio));
7959d34e6d8SKővágó, Zoltán     server = popts->has_server ? popts->server : NULL;
7969d34e6d8SKővágó, Zoltán 
7972c324b28SKővágó, Zoltán     g->dev = dev;
798ea9ebc2cSMarc-André Lureau 
7999d34e6d8SKővágó, Zoltán     QTAILQ_FOREACH(c, &pa_conns, list) {
8009d34e6d8SKővágó, Zoltán         if (server == NULL || c->server == NULL ?
8019d34e6d8SKővágó, Zoltán             server == c->server :
8029d34e6d8SKővágó, Zoltán             strcmp(server, c->server) == 0) {
8039d34e6d8SKővágó, Zoltán             g->conn = c;
804ea9ebc2cSMarc-André Lureau             break;
805ea9ebc2cSMarc-André Lureau         }
806ea9ebc2cSMarc-André Lureau     }
8079d34e6d8SKővágó, Zoltán     if (!g->conn) {
8089d34e6d8SKővágó, Zoltán         g->conn = qpa_conn_init(server);
809ea9ebc2cSMarc-André Lureau     }
8109d34e6d8SKővágó, Zoltán     if (!g->conn) {
8119d34e6d8SKővágó, Zoltán         g_free(g);
812ea9ebc2cSMarc-André Lureau         return NULL;
813b8e59f18Smalc     }
814b8e59f18Smalc 
8159d34e6d8SKővágó, Zoltán     ++g->conn->refcount;
8169d34e6d8SKővágó, Zoltán     return g;
8179d34e6d8SKővágó, Zoltán }
8189d34e6d8SKővágó, Zoltán 
8199d34e6d8SKővágó, Zoltán static void qpa_conn_fini(PAConnection *c)
8209d34e6d8SKővágó, Zoltán {
8219d34e6d8SKővágó, Zoltán     if (c->mainloop) {
8229d34e6d8SKővágó, Zoltán         pa_threaded_mainloop_stop(c->mainloop);
8239d34e6d8SKővágó, Zoltán     }
8249d34e6d8SKővágó, Zoltán 
8259d34e6d8SKővágó, Zoltán     if (c->context) {
8269d34e6d8SKővágó, Zoltán         pa_context_disconnect(c->context);
8279d34e6d8SKővágó, Zoltán         pa_context_unref(c->context);
8289d34e6d8SKővágó, Zoltán     }
8299d34e6d8SKővágó, Zoltán 
8309d34e6d8SKővágó, Zoltán     if (c->mainloop) {
8319d34e6d8SKővágó, Zoltán         pa_threaded_mainloop_free(c->mainloop);
8329d34e6d8SKővágó, Zoltán     }
8339d34e6d8SKővágó, Zoltán 
8349d34e6d8SKővágó, Zoltán     QTAILQ_REMOVE(&pa_conns, c, list);
8359d34e6d8SKővágó, Zoltán     g_free(c);
8369d34e6d8SKővágó, Zoltán }
8379d34e6d8SKővágó, Zoltán 
838b8e59f18Smalc static void qpa_audio_fini (void *opaque)
839b8e59f18Smalc {
840ea9ebc2cSMarc-André Lureau     paaudio *g = opaque;
8419d34e6d8SKővágó, Zoltán     PAConnection *c = g->conn;
842ea9ebc2cSMarc-André Lureau 
8439d34e6d8SKővágó, Zoltán     if (--c->refcount == 0) {
8449d34e6d8SKővágó, Zoltán         qpa_conn_fini(c);
845ea9ebc2cSMarc-André Lureau     }
846ea9ebc2cSMarc-André Lureau 
8479a644c4bSKővágó, Zoltán     g_free(g);
848b8e59f18Smalc }
849b8e59f18Smalc 
85035f4b58cSblueswir1 static struct audio_pcm_ops qpa_pcm_ops = {
8511dd3e4d1SJuan Quintela     .init_out = qpa_init_out,
8521dd3e4d1SJuan Quintela     .fini_out = qpa_fini_out,
85349ddd7e1SKővágó, Zoltán     .write    = qpa_write,
854337e8de6SKővágó, Zoltán     .get_buffer_out = qpa_get_buffer_out,
855337e8de6SKővágó, Zoltán     .put_buffer_out = qpa_write, /* pa handles it */
856571a8c52SKővágó, Zoltán     .volume_out = qpa_volume_out,
8571dd3e4d1SJuan Quintela 
8581dd3e4d1SJuan Quintela     .init_in  = qpa_init_in,
8591dd3e4d1SJuan Quintela     .fini_in  = qpa_fini_in,
86049ddd7e1SKővágó, Zoltán     .read     = qpa_read,
861337e8de6SKővágó, Zoltán     .get_buffer_in = qpa_get_buffer_in,
862337e8de6SKővágó, Zoltán     .put_buffer_in = qpa_put_buffer_in,
863571a8c52SKővágó, Zoltán     .volume_in = qpa_volume_in
864b8e59f18Smalc };
865b8e59f18Smalc 
866d3893a39SGerd Hoffmann static struct audio_driver pa_audio_driver = {
867bee37f32SJuan Quintela     .name           = "pa",
868bee37f32SJuan Quintela     .descr          = "http://www.pulseaudio.org/",
869bee37f32SJuan Quintela     .init           = qpa_audio_init,
870bee37f32SJuan Quintela     .fini           = qpa_audio_fini,
871bee37f32SJuan Quintela     .pcm_ops        = &qpa_pcm_ops,
8721a4ea1e3SMichael S. Tsirkin     .can_be_default = 1,
873bee37f32SJuan Quintela     .max_voices_out = INT_MAX,
874bee37f32SJuan Quintela     .max_voices_in  = INT_MAX,
875bee37f32SJuan Quintela     .voice_size_out = sizeof (PAVoiceOut),
8766e7a7f3dSMarc-André Lureau     .voice_size_in  = sizeof (PAVoiceIn),
877b8e59f18Smalc };
878d3893a39SGerd Hoffmann 
879d3893a39SGerd Hoffmann static void register_audio_pa(void)
880d3893a39SGerd Hoffmann {
881d3893a39SGerd Hoffmann     audio_driver_register(&pa_audio_driver);
882d3893a39SGerd Hoffmann }
883d3893a39SGerd Hoffmann type_init(register_audio_pa);
884