xref: /openbmc/qemu/audio/paaudio.c (revision acc3b63e1bdf806de1a520522dd43e494461d3bb)
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;
357520462bSKővágó, Zoltán     size_t samples;
36b8e59f18Smalc } PAVoiceOut;
37b8e59f18Smalc 
38b8e59f18Smalc typedef struct {
39b8e59f18Smalc     HWVoiceIn hw;
40ea9ebc2cSMarc-André Lureau     pa_stream *stream;
41ea9ebc2cSMarc-André Lureau     const void *read_data;
4249ddd7e1SKővágó, Zoltán     size_t read_length;
439a644c4bSKővágó, Zoltán     paaudio *g;
447520462bSKővágó, Zoltán     size_t samples;
45b8e59f18Smalc } PAVoiceIn;
46b8e59f18Smalc 
479d34e6d8SKővágó, Zoltán static void qpa_conn_fini(PAConnection *c);
4849dd6d0dSKővágó, Zoltán 
49b8e59f18Smalc static void GCC_FMT_ATTR (2, 3) qpa_logerr (int err, const char *fmt, ...)
50b8e59f18Smalc {
51b8e59f18Smalc     va_list ap;
52b8e59f18Smalc 
53b8e59f18Smalc     va_start (ap, fmt);
54b8e59f18Smalc     AUD_vlog (AUDIO_CAP, fmt, ap);
55b8e59f18Smalc     va_end (ap);
56b8e59f18Smalc 
57b8e59f18Smalc     AUD_log (AUDIO_CAP, "Reason: %s\n", pa_strerror (err));
58b8e59f18Smalc }
59b8e59f18Smalc 
608f473dd1SGerd Hoffmann #ifndef PA_CONTEXT_IS_GOOD
618f473dd1SGerd Hoffmann static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x)
628f473dd1SGerd Hoffmann {
638f473dd1SGerd Hoffmann     return
648f473dd1SGerd Hoffmann         x == PA_CONTEXT_CONNECTING ||
658f473dd1SGerd Hoffmann         x == PA_CONTEXT_AUTHORIZING ||
668f473dd1SGerd Hoffmann         x == PA_CONTEXT_SETTING_NAME ||
678f473dd1SGerd Hoffmann         x == PA_CONTEXT_READY;
688f473dd1SGerd Hoffmann }
698f473dd1SGerd Hoffmann #endif
708f473dd1SGerd Hoffmann 
718f473dd1SGerd Hoffmann #ifndef PA_STREAM_IS_GOOD
728f473dd1SGerd Hoffmann static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x)
738f473dd1SGerd Hoffmann {
748f473dd1SGerd Hoffmann     return
758f473dd1SGerd Hoffmann         x == PA_STREAM_CREATING ||
768f473dd1SGerd Hoffmann         x == PA_STREAM_READY;
778f473dd1SGerd Hoffmann }
788f473dd1SGerd Hoffmann #endif
798f473dd1SGerd Hoffmann 
8049ddd7e1SKővágó, Zoltán #define CHECK_SUCCESS_GOTO(c, expression, label, msg)           \
81ea9ebc2cSMarc-André Lureau     do {                                                        \
82ea9ebc2cSMarc-André Lureau         if (!(expression)) {                                    \
8349ddd7e1SKővágó, Zoltán             qpa_logerr(pa_context_errno((c)->context), msg);    \
84ea9ebc2cSMarc-André Lureau             goto label;                                         \
85ea9ebc2cSMarc-André Lureau         }                                                       \
862562755eSEric Blake     } while (0)
87ea9ebc2cSMarc-André Lureau 
8849ddd7e1SKővágó, Zoltán #define CHECK_DEAD_GOTO(c, stream, label, msg)                          \
89ea9ebc2cSMarc-André Lureau     do {                                                                \
90ea9ebc2cSMarc-André Lureau         if (!(c)->context || !PA_CONTEXT_IS_GOOD (pa_context_get_state((c)->context)) || \
91ea9ebc2cSMarc-André Lureau             !(stream) || !PA_STREAM_IS_GOOD (pa_stream_get_state ((stream)))) { \
92ea9ebc2cSMarc-André Lureau             if (((c)->context && pa_context_get_state ((c)->context) == PA_CONTEXT_FAILED) || \
93ea9ebc2cSMarc-André Lureau                 ((stream) && pa_stream_get_state ((stream)) == PA_STREAM_FAILED)) { \
9449ddd7e1SKővágó, Zoltán                 qpa_logerr(pa_context_errno((c)->context), msg);        \
95ea9ebc2cSMarc-André Lureau             } else {                                                    \
9649ddd7e1SKővágó, Zoltán                 qpa_logerr(PA_ERR_BADSTATE, msg);                       \
97ea9ebc2cSMarc-André Lureau             }                                                           \
98ea9ebc2cSMarc-André Lureau             goto label;                                                 \
99ea9ebc2cSMarc-André Lureau         }                                                               \
1002562755eSEric Blake     } while (0)
101ea9ebc2cSMarc-André Lureau 
102337e8de6SKővágó, Zoltán static void *qpa_get_buffer_in(HWVoiceIn *hw, size_t *size)
103337e8de6SKővágó, Zoltán {
104337e8de6SKővágó, Zoltán     PAVoiceIn *p = (PAVoiceIn *) hw;
105337e8de6SKővágó, Zoltán     PAConnection *c = p->g->conn;
106337e8de6SKővágó, Zoltán     int r;
107337e8de6SKővágó, Zoltán 
108337e8de6SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
109337e8de6SKővágó, Zoltán 
110337e8de6SKővágó, Zoltán     CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail,
111337e8de6SKővágó, Zoltán                     "pa_threaded_mainloop_lock failed\n");
112337e8de6SKővágó, Zoltán 
113337e8de6SKővágó, Zoltán     if (!p->read_length) {
114337e8de6SKővágó, Zoltán         r = pa_stream_peek(p->stream, &p->read_data, &p->read_length);
115337e8de6SKővágó, Zoltán         CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail,
116337e8de6SKővágó, Zoltán                            "pa_stream_peek failed\n");
117337e8de6SKővágó, Zoltán     }
118337e8de6SKővágó, Zoltán 
119337e8de6SKővágó, Zoltán     *size = MIN(p->read_length, *size);
120337e8de6SKővágó, Zoltán 
121337e8de6SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
122337e8de6SKővágó, Zoltán     return (void *) p->read_data;
123337e8de6SKővágó, Zoltán 
124337e8de6SKővágó, Zoltán unlock_and_fail:
125337e8de6SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
126337e8de6SKővágó, Zoltán     *size = 0;
127337e8de6SKővágó, Zoltán     return NULL;
128337e8de6SKővágó, Zoltán }
129337e8de6SKővágó, Zoltán 
130337e8de6SKővágó, Zoltán static void qpa_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)
131337e8de6SKővágó, Zoltán {
132337e8de6SKővágó, Zoltán     PAVoiceIn *p = (PAVoiceIn *) hw;
133337e8de6SKővágó, Zoltán     PAConnection *c = p->g->conn;
134337e8de6SKővágó, Zoltán     int r;
135337e8de6SKővágó, Zoltán 
136337e8de6SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
137337e8de6SKővágó, Zoltán 
138337e8de6SKővágó, Zoltán     CHECK_DEAD_GOTO(c, p->stream, unlock,
139337e8de6SKővágó, Zoltán                     "pa_threaded_mainloop_lock failed\n");
140337e8de6SKővágó, Zoltán 
141337e8de6SKővágó, Zoltán     assert(buf == p->read_data && size <= p->read_length);
142337e8de6SKővágó, Zoltán 
143337e8de6SKővágó, Zoltán     p->read_data += size;
144337e8de6SKővágó, Zoltán     p->read_length -= size;
145337e8de6SKővágó, Zoltán 
146337e8de6SKővágó, Zoltán     if (size && !p->read_length) {
147337e8de6SKővágó, Zoltán         r = pa_stream_drop(p->stream);
148337e8de6SKővágó, Zoltán         CHECK_SUCCESS_GOTO(c, r == 0, unlock, "pa_stream_drop failed\n");
149337e8de6SKővágó, Zoltán     }
150337e8de6SKővágó, Zoltán 
151337e8de6SKővágó, Zoltán unlock:
152337e8de6SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
153337e8de6SKővágó, Zoltán }
154337e8de6SKővágó, Zoltán 
15549ddd7e1SKővágó, Zoltán static size_t qpa_read(HWVoiceIn *hw, void *data, size_t length)
156ea9ebc2cSMarc-André Lureau {
15749ddd7e1SKővágó, Zoltán     PAVoiceIn *p = (PAVoiceIn *) hw;
1589d34e6d8SKővágó, Zoltán     PAConnection *c = p->g->conn;
159*acc3b63eSVolker Rümelin     size_t total = 0;
160ea9ebc2cSMarc-André Lureau 
1619d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
162ea9ebc2cSMarc-André Lureau 
16349ddd7e1SKővágó, Zoltán     CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail,
16449ddd7e1SKővágó, Zoltán                     "pa_threaded_mainloop_lock failed\n");
165ea9ebc2cSMarc-André Lureau 
166*acc3b63eSVolker Rümelin     while (total < length) {
167*acc3b63eSVolker Rümelin         size_t l;
168*acc3b63eSVolker Rümelin         int r;
169*acc3b63eSVolker Rümelin 
17049ddd7e1SKővágó, Zoltán         if (!p->read_length) {
171ea9ebc2cSMarc-André Lureau             r = pa_stream_peek(p->stream, &p->read_data, &p->read_length);
17249ddd7e1SKővágó, Zoltán             CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail,
17349ddd7e1SKővágó, Zoltán                                "pa_stream_peek failed\n");
174*acc3b63eSVolker Rümelin             if (!p->read_length) {
175*acc3b63eSVolker Rümelin                 /* buffer is empty */
176*acc3b63eSVolker Rümelin                 break;
177*acc3b63eSVolker Rümelin             }
178ea9ebc2cSMarc-André Lureau         }
179ea9ebc2cSMarc-André Lureau 
180*acc3b63eSVolker Rümelin         l = MIN(p->read_length, length - total);
181*acc3b63eSVolker Rümelin         memcpy((char *)data + total, p->read_data, l);
182ea9ebc2cSMarc-André Lureau 
18349ddd7e1SKővágó, Zoltán         p->read_data += l;
184ea9ebc2cSMarc-André Lureau         p->read_length -= l;
185*acc3b63eSVolker Rümelin         total += l;
186ea9ebc2cSMarc-André Lureau 
187ea9ebc2cSMarc-André Lureau         if (!p->read_length) {
188ea9ebc2cSMarc-André Lureau             r = pa_stream_drop(p->stream);
18949ddd7e1SKővágó, Zoltán             CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail,
19049ddd7e1SKővágó, Zoltán                                "pa_stream_drop failed\n");
191ea9ebc2cSMarc-André Lureau         }
192*acc3b63eSVolker Rümelin     }
193ea9ebc2cSMarc-André Lureau 
1949d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
195*acc3b63eSVolker Rümelin     return total;
196ea9ebc2cSMarc-André Lureau 
197ea9ebc2cSMarc-André Lureau unlock_and_fail:
1989d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
19949ddd7e1SKővágó, Zoltán     return 0;
200ea9ebc2cSMarc-André Lureau }
201ea9ebc2cSMarc-André Lureau 
202337e8de6SKővágó, Zoltán static void *qpa_get_buffer_out(HWVoiceOut *hw, size_t *size)
203337e8de6SKővágó, Zoltán {
204337e8de6SKővágó, Zoltán     PAVoiceOut *p = (PAVoiceOut *) hw;
205337e8de6SKővágó, Zoltán     PAConnection *c = p->g->conn;
206337e8de6SKővágó, Zoltán     void *ret;
207337e8de6SKővágó, Zoltán     int r;
208337e8de6SKővágó, Zoltán 
209337e8de6SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
210337e8de6SKővágó, Zoltán 
211337e8de6SKővágó, Zoltán     CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail,
212337e8de6SKővágó, Zoltán                     "pa_threaded_mainloop_lock failed\n");
213337e8de6SKővágó, Zoltán 
214337e8de6SKővágó, Zoltán     *size = -1;
215337e8de6SKővágó, Zoltán     r = pa_stream_begin_write(p->stream, &ret, size);
216337e8de6SKővágó, Zoltán     CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail,
217337e8de6SKővágó, Zoltán                        "pa_stream_begin_write failed\n");
218337e8de6SKővágó, Zoltán 
219337e8de6SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
220337e8de6SKővágó, Zoltán     return ret;
221337e8de6SKővágó, Zoltán 
222337e8de6SKővágó, Zoltán unlock_and_fail:
223337e8de6SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
224337e8de6SKővágó, Zoltán     *size = 0;
225337e8de6SKővágó, Zoltán     return NULL;
226337e8de6SKővágó, Zoltán }
227337e8de6SKővágó, Zoltán 
22849ddd7e1SKővágó, Zoltán static size_t qpa_write(HWVoiceOut *hw, void *data, size_t length)
229ea9ebc2cSMarc-André Lureau {
23049ddd7e1SKővágó, Zoltán     PAVoiceOut *p = (PAVoiceOut *) hw;
2319d34e6d8SKővágó, Zoltán     PAConnection *c = p->g->conn;
232ea9ebc2cSMarc-André Lureau     size_t l;
233ea9ebc2cSMarc-André Lureau     int r;
234ea9ebc2cSMarc-André Lureau 
23549ddd7e1SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
236ea9ebc2cSMarc-André Lureau 
23749ddd7e1SKővágó, Zoltán     CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail,
23849ddd7e1SKővágó, Zoltán                     "pa_threaded_mainloop_lock failed\n");
23949ddd7e1SKővágó, Zoltán 
24049ddd7e1SKővágó, Zoltán     l = pa_stream_writable_size(p->stream);
24149ddd7e1SKővágó, Zoltán 
24249ddd7e1SKővágó, Zoltán     CHECK_SUCCESS_GOTO(c, l != (size_t) -1, unlock_and_fail,
24349ddd7e1SKővágó, Zoltán                        "pa_stream_writable_size failed\n");
244ea9ebc2cSMarc-André Lureau 
245ea9ebc2cSMarc-André Lureau     if (l > length) {
246ea9ebc2cSMarc-André Lureau         l = length;
247ea9ebc2cSMarc-André Lureau     }
248ea9ebc2cSMarc-André Lureau 
249ea9ebc2cSMarc-André Lureau     r = pa_stream_write(p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE);
25049ddd7e1SKővágó, Zoltán     CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail, "pa_stream_write failed\n");
251ea9ebc2cSMarc-André Lureau 
2529d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
25349ddd7e1SKővágó, Zoltán     return l;
254ea9ebc2cSMarc-André Lureau 
255ea9ebc2cSMarc-André Lureau unlock_and_fail:
2569d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
257b8e59f18Smalc     return 0;
258b8e59f18Smalc }
259b8e59f18Smalc 
26085bc5852SKővágó, Zoltán static pa_sample_format_t audfmt_to_pa (AudioFormat afmt, int endianness)
261b8e59f18Smalc {
262b8e59f18Smalc     int format;
263b8e59f18Smalc 
264b8e59f18Smalc     switch (afmt) {
26585bc5852SKővágó, Zoltán     case AUDIO_FORMAT_S8:
26685bc5852SKővágó, Zoltán     case AUDIO_FORMAT_U8:
267b8e59f18Smalc         format = PA_SAMPLE_U8;
268b8e59f18Smalc         break;
26985bc5852SKővágó, Zoltán     case AUDIO_FORMAT_S16:
27085bc5852SKővágó, Zoltán     case AUDIO_FORMAT_U16:
271b8e59f18Smalc         format = endianness ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE;
272b8e59f18Smalc         break;
27385bc5852SKővágó, Zoltán     case AUDIO_FORMAT_S32:
27485bc5852SKővágó, Zoltán     case AUDIO_FORMAT_U32:
275b8e59f18Smalc         format = endianness ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE;
276b8e59f18Smalc         break;
277b8e59f18Smalc     default:
278b8e59f18Smalc         dolog ("Internal logic error: Bad audio format %d\n", afmt);
279b8e59f18Smalc         format = PA_SAMPLE_U8;
280b8e59f18Smalc         break;
281b8e59f18Smalc     }
282b8e59f18Smalc     return format;
283b8e59f18Smalc }
284b8e59f18Smalc 
28585bc5852SKővágó, Zoltán static AudioFormat pa_to_audfmt (pa_sample_format_t fmt, int *endianness)
286b8e59f18Smalc {
287b8e59f18Smalc     switch (fmt) {
288b8e59f18Smalc     case PA_SAMPLE_U8:
28985bc5852SKővágó, Zoltán         return AUDIO_FORMAT_U8;
290b8e59f18Smalc     case PA_SAMPLE_S16BE:
291b8e59f18Smalc         *endianness = 1;
29285bc5852SKővágó, Zoltán         return AUDIO_FORMAT_S16;
293b8e59f18Smalc     case PA_SAMPLE_S16LE:
294b8e59f18Smalc         *endianness = 0;
29585bc5852SKővágó, Zoltán         return AUDIO_FORMAT_S16;
296b8e59f18Smalc     case PA_SAMPLE_S32BE:
297b8e59f18Smalc         *endianness = 1;
29885bc5852SKővágó, Zoltán         return AUDIO_FORMAT_S32;
299b8e59f18Smalc     case PA_SAMPLE_S32LE:
300b8e59f18Smalc         *endianness = 0;
30185bc5852SKővágó, Zoltán         return AUDIO_FORMAT_S32;
302b8e59f18Smalc     default:
303b8e59f18Smalc         dolog ("Internal logic error: Bad pa_sample_format %d\n", fmt);
30485bc5852SKővágó, Zoltán         return AUDIO_FORMAT_U8;
305b8e59f18Smalc     }
306b8e59f18Smalc }
307b8e59f18Smalc 
308ea9ebc2cSMarc-André Lureau static void context_state_cb (pa_context *c, void *userdata)
309ea9ebc2cSMarc-André Lureau {
3109d34e6d8SKővágó, Zoltán     PAConnection *conn = userdata;
311ea9ebc2cSMarc-André Lureau 
312ea9ebc2cSMarc-André Lureau     switch (pa_context_get_state(c)) {
313ea9ebc2cSMarc-André Lureau     case PA_CONTEXT_READY:
314ea9ebc2cSMarc-André Lureau     case PA_CONTEXT_TERMINATED:
315ea9ebc2cSMarc-André Lureau     case PA_CONTEXT_FAILED:
3169d34e6d8SKővágó, Zoltán         pa_threaded_mainloop_signal(conn->mainloop, 0);
317ea9ebc2cSMarc-André Lureau         break;
318ea9ebc2cSMarc-André Lureau 
319ea9ebc2cSMarc-André Lureau     case PA_CONTEXT_UNCONNECTED:
320ea9ebc2cSMarc-André Lureau     case PA_CONTEXT_CONNECTING:
321ea9ebc2cSMarc-André Lureau     case PA_CONTEXT_AUTHORIZING:
322ea9ebc2cSMarc-André Lureau     case PA_CONTEXT_SETTING_NAME:
323ea9ebc2cSMarc-André Lureau         break;
324ea9ebc2cSMarc-André Lureau     }
325ea9ebc2cSMarc-André Lureau }
326ea9ebc2cSMarc-André Lureau 
327ea9ebc2cSMarc-André Lureau static void stream_state_cb (pa_stream *s, void * userdata)
328ea9ebc2cSMarc-André Lureau {
3299d34e6d8SKővágó, Zoltán     PAConnection *c = userdata;
330ea9ebc2cSMarc-André Lureau 
331ea9ebc2cSMarc-André Lureau     switch (pa_stream_get_state (s)) {
332ea9ebc2cSMarc-André Lureau 
333ea9ebc2cSMarc-André Lureau     case PA_STREAM_READY:
334ea9ebc2cSMarc-André Lureau     case PA_STREAM_FAILED:
335ea9ebc2cSMarc-André Lureau     case PA_STREAM_TERMINATED:
3369d34e6d8SKővágó, Zoltán         pa_threaded_mainloop_signal(c->mainloop, 0);
337ea9ebc2cSMarc-André Lureau         break;
338ea9ebc2cSMarc-André Lureau 
339ea9ebc2cSMarc-André Lureau     case PA_STREAM_UNCONNECTED:
340ea9ebc2cSMarc-André Lureau     case PA_STREAM_CREATING:
341ea9ebc2cSMarc-André Lureau         break;
342ea9ebc2cSMarc-André Lureau     }
343ea9ebc2cSMarc-André Lureau }
344ea9ebc2cSMarc-André Lureau 
345ea9ebc2cSMarc-André Lureau static pa_stream *qpa_simple_new (
3469d34e6d8SKővágó, Zoltán         PAConnection *c,
347ea9ebc2cSMarc-André Lureau         const char *name,
348ea9ebc2cSMarc-André Lureau         pa_stream_direction_t dir,
349ea9ebc2cSMarc-André Lureau         const char *dev,
350ea9ebc2cSMarc-André Lureau         const pa_sample_spec *ss,
351ea9ebc2cSMarc-André Lureau         const pa_buffer_attr *attr,
352ea9ebc2cSMarc-André Lureau         int *rerror)
353ea9ebc2cSMarc-André Lureau {
354ea9ebc2cSMarc-André Lureau     int r;
3550cf13e36SKővágó, Zoltán     pa_stream *stream = NULL;
3569d34e6d8SKővágó, Zoltán     pa_stream_flags_t flags;
3570cf13e36SKővágó, Zoltán     pa_channel_map map;
358ea9ebc2cSMarc-André Lureau 
3599d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
360ea9ebc2cSMarc-André Lureau 
3610cf13e36SKővágó, Zoltán     pa_channel_map_init(&map);
3620cf13e36SKővágó, Zoltán     map.channels = ss->channels;
3630cf13e36SKővágó, Zoltán 
3640cf13e36SKővágó, Zoltán     /*
3650cf13e36SKővágó, Zoltán      * TODO: This currently expects the only frontend supporting more than 2
3660cf13e36SKővágó, Zoltán      * channels is the usb-audio.  We will need some means to set channel
3670cf13e36SKővágó, Zoltán      * order when a new frontend gains multi-channel support.
3680cf13e36SKővágó, Zoltán      */
3690cf13e36SKővágó, Zoltán     switch (ss->channels) {
3700cf13e36SKővágó, Zoltán     case 1:
3710cf13e36SKővágó, Zoltán         map.map[0] = PA_CHANNEL_POSITION_MONO;
3720cf13e36SKővágó, Zoltán         break;
3730cf13e36SKővágó, Zoltán 
3740cf13e36SKővágó, Zoltán     case 2:
3750cf13e36SKővágó, Zoltán         map.map[0] = PA_CHANNEL_POSITION_LEFT;
3760cf13e36SKővágó, Zoltán         map.map[1] = PA_CHANNEL_POSITION_RIGHT;
3770cf13e36SKővágó, Zoltán         break;
3780cf13e36SKővágó, Zoltán 
3790cf13e36SKővágó, Zoltán     case 6:
3800cf13e36SKővágó, Zoltán         map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
3810cf13e36SKővágó, Zoltán         map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
3820cf13e36SKővágó, Zoltán         map.map[2] = PA_CHANNEL_POSITION_CENTER;
3830cf13e36SKővágó, Zoltán         map.map[3] = PA_CHANNEL_POSITION_LFE;
3840cf13e36SKővágó, Zoltán         map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT;
3850cf13e36SKővágó, Zoltán         map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT;
3860cf13e36SKővágó, Zoltán         break;
3870cf13e36SKővágó, Zoltán 
3880cf13e36SKővágó, Zoltán     case 8:
3890cf13e36SKővágó, Zoltán         map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
3900cf13e36SKővágó, Zoltán         map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
3910cf13e36SKővágó, Zoltán         map.map[2] = PA_CHANNEL_POSITION_CENTER;
3920cf13e36SKővágó, Zoltán         map.map[3] = PA_CHANNEL_POSITION_LFE;
3930cf13e36SKővágó, Zoltán         map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT;
3940cf13e36SKővágó, Zoltán         map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT;
3950cf13e36SKővágó, Zoltán         map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT;
3960cf13e36SKővágó, Zoltán         map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT;
39756089565SPaolo Bonzini         break;
3980cf13e36SKővágó, Zoltán 
3990cf13e36SKővágó, Zoltán     default:
4000cf13e36SKővágó, Zoltán         dolog("Internal error: unsupported channel count %d\n", ss->channels);
4010cf13e36SKővágó, Zoltán         goto fail;
4020cf13e36SKővágó, Zoltán     }
4030cf13e36SKővágó, Zoltán 
4040cf13e36SKővágó, Zoltán     stream = pa_stream_new(c->context, name, ss, &map);
405ea9ebc2cSMarc-André Lureau     if (!stream) {
406ea9ebc2cSMarc-André Lureau         goto fail;
407ea9ebc2cSMarc-André Lureau     }
408ea9ebc2cSMarc-André Lureau 
4099d34e6d8SKővágó, Zoltán     pa_stream_set_state_callback(stream, stream_state_cb, c);
4109d34e6d8SKővágó, Zoltán 
4119d34e6d8SKővágó, Zoltán     flags =
4129d34e6d8SKővágó, Zoltán         PA_STREAM_INTERPOLATE_TIMING
41310d5e750SKővágó, Zoltán         | PA_STREAM_AUTO_TIMING_UPDATE
41410d5e750SKővágó, Zoltán         | PA_STREAM_EARLY_REQUESTS;
415ea9ebc2cSMarc-André Lureau 
4168a435f74SKővágó, Zoltán     if (dev) {
4178a435f74SKővágó, Zoltán         /* don't move the stream if the user specified a sink/source */
4188a435f74SKővágó, Zoltán         flags |= PA_STREAM_DONT_MOVE;
4198a435f74SKővágó, Zoltán     }
4208a435f74SKővágó, Zoltán 
421ea9ebc2cSMarc-André Lureau     if (dir == PA_STREAM_PLAYBACK) {
4229d34e6d8SKővágó, Zoltán         r = pa_stream_connect_playback(stream, dev, attr, flags, NULL, NULL);
423ea9ebc2cSMarc-André Lureau     } else {
4249d34e6d8SKővágó, Zoltán         r = pa_stream_connect_record(stream, dev, attr, flags);
425ea9ebc2cSMarc-André Lureau     }
426ea9ebc2cSMarc-André Lureau 
427ea9ebc2cSMarc-André Lureau     if (r < 0) {
428ea9ebc2cSMarc-André Lureau       goto fail;
429ea9ebc2cSMarc-André Lureau     }
430ea9ebc2cSMarc-André Lureau 
4319d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
432ea9ebc2cSMarc-André Lureau 
433ea9ebc2cSMarc-André Lureau     return stream;
434ea9ebc2cSMarc-André Lureau 
435ea9ebc2cSMarc-André Lureau fail:
4369d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
437ea9ebc2cSMarc-André Lureau 
438ea9ebc2cSMarc-André Lureau     if (stream) {
439ea9ebc2cSMarc-André Lureau         pa_stream_unref (stream);
440ea9ebc2cSMarc-André Lureau     }
441ea9ebc2cSMarc-André Lureau 
4429d34e6d8SKővágó, Zoltán     *rerror = pa_context_errno(c->context);
443ea9ebc2cSMarc-André Lureau 
444ea9ebc2cSMarc-André Lureau     return NULL;
445ea9ebc2cSMarc-André Lureau }
446ea9ebc2cSMarc-André Lureau 
4475706db1dSKővágó, Zoltán static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as,
4485706db1dSKővágó, Zoltán                         void *drv_opaque)
449b8e59f18Smalc {
450b8e59f18Smalc     int error;
4519a644c4bSKővágó, Zoltán     pa_sample_spec ss;
4529a644c4bSKővágó, Zoltán     pa_buffer_attr ba;
4531ea879e5Smalc     struct audsettings obt_as = *as;
454b8e59f18Smalc     PAVoiceOut *pa = (PAVoiceOut *) hw;
4559a644c4bSKővágó, Zoltán     paaudio *g = pa->g = drv_opaque;
4562c324b28SKővágó, Zoltán     AudiodevPaOptions *popts = &g->dev->u.pa;
4572c324b28SKővágó, Zoltán     AudiodevPaPerDirectionOptions *ppdo = popts->out;
4589d34e6d8SKővágó, Zoltán     PAConnection *c = g->conn;
459b8e59f18Smalc 
460b8e59f18Smalc     ss.format = audfmt_to_pa (as->fmt, as->endianness);
461b8e59f18Smalc     ss.channels = as->nchannels;
462b8e59f18Smalc     ss.rate = as->freq;
463b8e59f18Smalc 
464f6142777SMartin Schrodt     ba.tlength = pa_usec_to_bytes(ppdo->latency, &ss);
465f6142777SMartin Schrodt     ba.minreq = -1;
466e6d16fa4SGerd Hoffmann     ba.maxlength = -1;
467e6d16fa4SGerd Hoffmann     ba.prebuf = -1;
468e6d16fa4SGerd Hoffmann 
469b8e59f18Smalc     obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness);
470b8e59f18Smalc 
471ea9ebc2cSMarc-André Lureau     pa->stream = qpa_simple_new (
4729d34e6d8SKővágó, Zoltán         c,
473f47dffe8SKővágó, Zoltán         ppdo->has_stream_name ? ppdo->stream_name : g->dev->id,
474b8e59f18Smalc         PA_STREAM_PLAYBACK,
4752c324b28SKővágó, Zoltán         ppdo->has_name ? ppdo->name : NULL,
476b8e59f18Smalc         &ss,
477e6d16fa4SGerd Hoffmann         &ba,                    /* buffering attributes */
478b8e59f18Smalc         &error
479b8e59f18Smalc         );
480ea9ebc2cSMarc-André Lureau     if (!pa->stream) {
481b8e59f18Smalc         qpa_logerr (error, "pa_simple_new for playback failed\n");
482b8e59f18Smalc         goto fail1;
483b8e59f18Smalc     }
484b8e59f18Smalc 
485b8e59f18Smalc     audio_pcm_init_info (&hw->info, &obt_as);
4862c324b28SKővágó, Zoltán     hw->samples = pa->samples = audio_buffer_samples(
487baea032eSMartin Schrodt         qapi_AudiodevPaPerDirectionOptions_base(ppdo),
488baea032eSMartin Schrodt         &obt_as, ppdo->buffer_length);
489b8e59f18Smalc 
490b8e59f18Smalc     return 0;
491b8e59f18Smalc 
492b8e59f18Smalc  fail1:
493b8e59f18Smalc     return -1;
494b8e59f18Smalc }
495b8e59f18Smalc 
4965706db1dSKővágó, Zoltán static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
497b8e59f18Smalc {
498b8e59f18Smalc     int error;
4999a644c4bSKővágó, Zoltán     pa_sample_spec ss;
500ade10301SMartin Schrodt     pa_buffer_attr ba;
5011ea879e5Smalc     struct audsettings obt_as = *as;
502b8e59f18Smalc     PAVoiceIn *pa = (PAVoiceIn *) hw;
5039a644c4bSKővágó, Zoltán     paaudio *g = pa->g = drv_opaque;
5042c324b28SKővágó, Zoltán     AudiodevPaOptions *popts = &g->dev->u.pa;
5052c324b28SKővágó, Zoltán     AudiodevPaPerDirectionOptions *ppdo = popts->in;
5069d34e6d8SKővágó, Zoltán     PAConnection *c = g->conn;
507b8e59f18Smalc 
508b8e59f18Smalc     ss.format = audfmt_to_pa (as->fmt, as->endianness);
509b8e59f18Smalc     ss.channels = as->nchannels;
510b8e59f18Smalc     ss.rate = as->freq;
511b8e59f18Smalc 
512ade10301SMartin Schrodt     ba.fragsize = pa_usec_to_bytes(ppdo->latency, &ss);
51358c15e52SMartin Schrodt     ba.maxlength = pa_usec_to_bytes(ppdo->latency * 2, &ss);
514ade10301SMartin Schrodt     ba.minreq = -1;
515ade10301SMartin Schrodt     ba.prebuf = -1;
516ade10301SMartin Schrodt 
517b8e59f18Smalc     obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness);
518b8e59f18Smalc 
519ea9ebc2cSMarc-André Lureau     pa->stream = qpa_simple_new (
5209d34e6d8SKővágó, Zoltán         c,
521f47dffe8SKővágó, Zoltán         ppdo->has_stream_name ? ppdo->stream_name : g->dev->id,
522b8e59f18Smalc         PA_STREAM_RECORD,
5232c324b28SKővágó, Zoltán         ppdo->has_name ? ppdo->name : NULL,
524b8e59f18Smalc         &ss,
525ade10301SMartin Schrodt         &ba,                    /* buffering attributes */
526b8e59f18Smalc         &error
527b8e59f18Smalc         );
528ea9ebc2cSMarc-André Lureau     if (!pa->stream) {
529b8e59f18Smalc         qpa_logerr (error, "pa_simple_new for capture failed\n");
530b8e59f18Smalc         goto fail1;
531b8e59f18Smalc     }
532b8e59f18Smalc 
533b8e59f18Smalc     audio_pcm_init_info (&hw->info, &obt_as);
5342c324b28SKővágó, Zoltán     hw->samples = pa->samples = audio_buffer_samples(
535baea032eSMartin Schrodt         qapi_AudiodevPaPerDirectionOptions_base(ppdo),
536baea032eSMartin Schrodt         &obt_as, ppdo->buffer_length);
537b8e59f18Smalc 
538b8e59f18Smalc     return 0;
539b8e59f18Smalc 
540b8e59f18Smalc  fail1:
541b8e59f18Smalc     return -1;
542b8e59f18Smalc }
543b8e59f18Smalc 
5448692bf7dSKővágó, Zoltán static void qpa_simple_disconnect(PAConnection *c, pa_stream *stream)
5458692bf7dSKővágó, Zoltán {
5468692bf7dSKővágó, Zoltán     int err;
5478692bf7dSKővágó, Zoltán 
5488692bf7dSKővágó, Zoltán     /*
5498692bf7dSKővágó, Zoltán      * wait until actually connects. workaround pa bug #247
5508692bf7dSKővágó, Zoltán      * https://gitlab.freedesktop.org/pulseaudio/pulseaudio/issues/247
5518692bf7dSKővágó, Zoltán      */
5528692bf7dSKővágó, Zoltán     while (pa_stream_get_state(stream) == PA_STREAM_CREATING) {
5538692bf7dSKővágó, Zoltán         pa_threaded_mainloop_wait(c->mainloop);
5548692bf7dSKővágó, Zoltán     }
5558692bf7dSKővágó, Zoltán 
5568692bf7dSKővágó, Zoltán     err = pa_stream_disconnect(stream);
5578692bf7dSKővágó, Zoltán     if (err != 0) {
5588692bf7dSKővágó, Zoltán         dolog("Failed to disconnect! err=%d\n", err);
5598692bf7dSKővágó, Zoltán     }
5608692bf7dSKővágó, Zoltán     pa_stream_unref(stream);
5618692bf7dSKővágó, Zoltán }
5628692bf7dSKővágó, Zoltán 
563b8e59f18Smalc static void qpa_fini_out (HWVoiceOut *hw)
564b8e59f18Smalc {
565b8e59f18Smalc     PAVoiceOut *pa = (PAVoiceOut *) hw;
566b8e59f18Smalc 
567ea9ebc2cSMarc-André Lureau     if (pa->stream) {
5684db3e634SVolker Rümelin         PAConnection *c = pa->g->conn;
5694db3e634SVolker Rümelin 
5704db3e634SVolker Rümelin         pa_threaded_mainloop_lock(c->mainloop);
5714db3e634SVolker Rümelin         qpa_simple_disconnect(c, pa->stream);
572ea9ebc2cSMarc-André Lureau         pa->stream = NULL;
5734db3e634SVolker Rümelin         pa_threaded_mainloop_unlock(c->mainloop);
574b8e59f18Smalc     }
575b8e59f18Smalc }
576b8e59f18Smalc 
577b8e59f18Smalc static void qpa_fini_in (HWVoiceIn *hw)
578b8e59f18Smalc {
579b8e59f18Smalc     PAVoiceIn *pa = (PAVoiceIn *) hw;
580b8e59f18Smalc 
581ea9ebc2cSMarc-André Lureau     if (pa->stream) {
5824db3e634SVolker Rümelin         PAConnection *c = pa->g->conn;
5834db3e634SVolker Rümelin 
5844db3e634SVolker Rümelin         pa_threaded_mainloop_lock(c->mainloop);
5854db3e634SVolker Rümelin         if (pa->read_length) {
5864db3e634SVolker Rümelin             int r = pa_stream_drop(pa->stream);
5874db3e634SVolker Rümelin             if (r) {
5884db3e634SVolker Rümelin                 qpa_logerr(pa_context_errno(c->context),
5894db3e634SVolker Rümelin                            "pa_stream_drop failed\n");
5904db3e634SVolker Rümelin             }
5914db3e634SVolker Rümelin             pa->read_length = 0;
5924db3e634SVolker Rümelin         }
5934db3e634SVolker Rümelin         qpa_simple_disconnect(c, pa->stream);
594ea9ebc2cSMarc-André Lureau         pa->stream = NULL;
5954db3e634SVolker Rümelin         pa_threaded_mainloop_unlock(c->mainloop);
596b8e59f18Smalc     }
597b8e59f18Smalc }
598b8e59f18Smalc 
599cecc1e79SKővágó, Zoltán static void qpa_volume_out(HWVoiceOut *hw, Volume *vol)
600b8e59f18Smalc {
6016e7a7f3dSMarc-André Lureau     PAVoiceOut *pa = (PAVoiceOut *) hw;
6026e7a7f3dSMarc-André Lureau     pa_operation *op;
6036e7a7f3dSMarc-André Lureau     pa_cvolume v;
6049d34e6d8SKővágó, Zoltán     PAConnection *c = pa->g->conn;
605cecc1e79SKővágó, Zoltán     int i;
6066e7a7f3dSMarc-André Lureau 
6078f473dd1SGerd Hoffmann #ifdef PA_CHECK_VERSION    /* macro is present in 0.9.16+ */
6088f473dd1SGerd Hoffmann     pa_cvolume_init (&v);  /* function is present in 0.9.13+ */
6098f473dd1SGerd Hoffmann #endif
6106e7a7f3dSMarc-André Lureau 
611cecc1e79SKővágó, Zoltán     v.channels = vol->channels;
612cecc1e79SKővágó, Zoltán     for (i = 0; i < vol->channels; ++i) {
613cecc1e79SKővágó, Zoltán         v.values[i] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->vol[i]) / 255;
614cecc1e79SKővágó, Zoltán     }
6156e7a7f3dSMarc-André Lureau 
6169d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
6176e7a7f3dSMarc-André Lureau 
6189d34e6d8SKővágó, Zoltán     op = pa_context_set_sink_input_volume(c->context,
6196e7a7f3dSMarc-André Lureau                                           pa_stream_get_index(pa->stream),
6206e7a7f3dSMarc-André Lureau                                           &v, NULL, NULL);
6219d34e6d8SKővágó, Zoltán     if (!op) {
6229d34e6d8SKővágó, Zoltán         qpa_logerr(pa_context_errno(c->context),
6236e7a7f3dSMarc-André Lureau                    "set_sink_input_volume() failed\n");
6249d34e6d8SKővágó, Zoltán     } else {
6256e7a7f3dSMarc-André Lureau         pa_operation_unref(op);
6269d34e6d8SKővágó, Zoltán     }
6276e7a7f3dSMarc-André Lureau 
6289d34e6d8SKővágó, Zoltán     op = pa_context_set_sink_input_mute(c->context,
6296e7a7f3dSMarc-André Lureau                                         pa_stream_get_index(pa->stream),
630571a8c52SKővágó, Zoltán                                         vol->mute, NULL, NULL);
6316e7a7f3dSMarc-André Lureau     if (!op) {
6329d34e6d8SKővágó, Zoltán         qpa_logerr(pa_context_errno(c->context),
6336e7a7f3dSMarc-André Lureau                    "set_sink_input_mute() failed\n");
6346e7a7f3dSMarc-André Lureau     } else {
6356e7a7f3dSMarc-André Lureau         pa_operation_unref(op);
6366e7a7f3dSMarc-André Lureau     }
6376e7a7f3dSMarc-André Lureau 
6389d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
6396e7a7f3dSMarc-André Lureau }
640b8e59f18Smalc 
641cecc1e79SKővágó, Zoltán static void qpa_volume_in(HWVoiceIn *hw, Volume *vol)
642b8e59f18Smalc {
6436e7a7f3dSMarc-André Lureau     PAVoiceIn *pa = (PAVoiceIn *) hw;
6446e7a7f3dSMarc-André Lureau     pa_operation *op;
6456e7a7f3dSMarc-André Lureau     pa_cvolume v;
6469d34e6d8SKővágó, Zoltán     PAConnection *c = pa->g->conn;
647cecc1e79SKővágó, Zoltán     int i;
6486e7a7f3dSMarc-André Lureau 
6498f473dd1SGerd Hoffmann #ifdef PA_CHECK_VERSION
6506e7a7f3dSMarc-André Lureau     pa_cvolume_init (&v);
6518f473dd1SGerd Hoffmann #endif
6526e7a7f3dSMarc-André Lureau 
653cecc1e79SKővágó, Zoltán     v.channels = vol->channels;
654cecc1e79SKővágó, Zoltán     for (i = 0; i < vol->channels; ++i) {
655cecc1e79SKővágó, Zoltán         v.values[i] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->vol[i]) / 255;
656cecc1e79SKővágó, Zoltán     }
6576e7a7f3dSMarc-André Lureau 
6589d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
6596e7a7f3dSMarc-André Lureau 
6609d34e6d8SKővágó, Zoltán     op = pa_context_set_source_output_volume(c->context,
661e58ff62dSPeter Krempa         pa_stream_get_index(pa->stream),
6626e7a7f3dSMarc-André Lureau         &v, NULL, NULL);
6636e7a7f3dSMarc-André Lureau     if (!op) {
6649d34e6d8SKővágó, Zoltán         qpa_logerr(pa_context_errno(c->context),
665e58ff62dSPeter Krempa                    "set_source_output_volume() failed\n");
6666e7a7f3dSMarc-André Lureau     } else {
6676e7a7f3dSMarc-André Lureau         pa_operation_unref(op);
6686e7a7f3dSMarc-André Lureau     }
6696e7a7f3dSMarc-André Lureau 
6709d34e6d8SKővágó, Zoltán     op = pa_context_set_source_output_mute(c->context,
6716e7a7f3dSMarc-André Lureau         pa_stream_get_index(pa->stream),
672571a8c52SKővágó, Zoltán         vol->mute, NULL, NULL);
6736e7a7f3dSMarc-André Lureau     if (!op) {
6749d34e6d8SKővágó, Zoltán         qpa_logerr(pa_context_errno(c->context),
675e58ff62dSPeter Krempa                    "set_source_output_mute() failed\n");
6766e7a7f3dSMarc-André Lureau     } else {
6776e7a7f3dSMarc-André Lureau         pa_operation_unref(op);
6786e7a7f3dSMarc-André Lureau     }
6796e7a7f3dSMarc-André Lureau 
6809d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
6816e7a7f3dSMarc-André Lureau }
682b8e59f18Smalc 
683baea032eSMartin Schrodt static int qpa_validate_per_direction_opts(Audiodev *dev,
684baea032eSMartin Schrodt                                            AudiodevPaPerDirectionOptions *pdo)
685baea032eSMartin Schrodt {
686baea032eSMartin Schrodt     if (!pdo->has_buffer_length) {
687baea032eSMartin Schrodt         pdo->has_buffer_length = true;
688baea032eSMartin Schrodt         pdo->buffer_length = 46440;
689baea032eSMartin Schrodt     }
690f6142777SMartin Schrodt     if (!pdo->has_latency) {
691f6142777SMartin Schrodt         pdo->has_latency = true;
692f6142777SMartin Schrodt         pdo->latency = 15000;
693f6142777SMartin Schrodt     }
694baea032eSMartin Schrodt     return 1;
695baea032eSMartin Schrodt }
696baea032eSMartin Schrodt 
6979d34e6d8SKővágó, Zoltán /* common */
6989d34e6d8SKővágó, Zoltán static void *qpa_conn_init(const char *server)
6999d34e6d8SKővágó, Zoltán {
7003443ad4eSKővágó, Zoltán     const char *vm_name;
7019d34e6d8SKővágó, Zoltán     PAConnection *c = g_malloc0(sizeof(PAConnection));
7029d34e6d8SKővágó, Zoltán     QTAILQ_INSERT_TAIL(&pa_conns, c, list);
7039d34e6d8SKővágó, Zoltán 
7049d34e6d8SKővágó, Zoltán     c->mainloop = pa_threaded_mainloop_new();
7059d34e6d8SKővágó, Zoltán     if (!c->mainloop) {
7069d34e6d8SKővágó, Zoltán         goto fail;
7079d34e6d8SKővágó, Zoltán     }
7089d34e6d8SKővágó, Zoltán 
7093443ad4eSKővágó, Zoltán     vm_name = qemu_get_vm_name();
7109d34e6d8SKővágó, Zoltán     c->context = pa_context_new(pa_threaded_mainloop_get_api(c->mainloop),
7113443ad4eSKővágó, Zoltán                                 vm_name ? vm_name : "qemu");
7129d34e6d8SKővágó, Zoltán     if (!c->context) {
7139d34e6d8SKővágó, Zoltán         goto fail;
7149d34e6d8SKővágó, Zoltán     }
7159d34e6d8SKővágó, Zoltán 
7169d34e6d8SKővágó, Zoltán     pa_context_set_state_callback(c->context, context_state_cb, c);
7179d34e6d8SKővágó, Zoltán 
7189d34e6d8SKővágó, Zoltán     if (pa_context_connect(c->context, server, 0, NULL) < 0) {
7199d34e6d8SKővágó, Zoltán         qpa_logerr(pa_context_errno(c->context),
7209d34e6d8SKővágó, Zoltán                    "pa_context_connect() failed\n");
7219d34e6d8SKővágó, Zoltán         goto fail;
7229d34e6d8SKővágó, Zoltán     }
7239d34e6d8SKővágó, Zoltán 
7249d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_lock(c->mainloop);
7259d34e6d8SKővágó, Zoltán 
7269d34e6d8SKővágó, Zoltán     if (pa_threaded_mainloop_start(c->mainloop) < 0) {
7279d34e6d8SKővágó, Zoltán         goto unlock_and_fail;
7289d34e6d8SKővágó, Zoltán     }
7299d34e6d8SKővágó, Zoltán 
7309d34e6d8SKővágó, Zoltán     for (;;) {
7319d34e6d8SKővágó, Zoltán         pa_context_state_t state;
7329d34e6d8SKővágó, Zoltán 
7339d34e6d8SKővágó, Zoltán         state = pa_context_get_state(c->context);
7349d34e6d8SKővágó, Zoltán 
7359d34e6d8SKővágó, Zoltán         if (state == PA_CONTEXT_READY) {
7369d34e6d8SKővágó, Zoltán             break;
7379d34e6d8SKővágó, Zoltán         }
7389d34e6d8SKővágó, Zoltán 
7399d34e6d8SKővágó, Zoltán         if (!PA_CONTEXT_IS_GOOD(state)) {
7409d34e6d8SKővágó, Zoltán             qpa_logerr(pa_context_errno(c->context),
7419d34e6d8SKővágó, Zoltán                        "Wrong context state\n");
7429d34e6d8SKővágó, Zoltán             goto unlock_and_fail;
7439d34e6d8SKővágó, Zoltán         }
7449d34e6d8SKővágó, Zoltán 
7459d34e6d8SKővágó, Zoltán         /* Wait until the context is ready */
7469d34e6d8SKővágó, Zoltán         pa_threaded_mainloop_wait(c->mainloop);
7479d34e6d8SKővágó, Zoltán     }
7489d34e6d8SKővágó, Zoltán 
7499d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
7509d34e6d8SKővágó, Zoltán     return c;
7519d34e6d8SKővágó, Zoltán 
7529d34e6d8SKővágó, Zoltán unlock_and_fail:
7539d34e6d8SKővágó, Zoltán     pa_threaded_mainloop_unlock(c->mainloop);
7549d34e6d8SKővágó, Zoltán fail:
7559d34e6d8SKővágó, Zoltán     AUD_log (AUDIO_CAP, "Failed to initialize PA context");
7569d34e6d8SKővágó, Zoltán     qpa_conn_fini(c);
7579d34e6d8SKővágó, Zoltán     return NULL;
7589d34e6d8SKővágó, Zoltán }
7599d34e6d8SKővágó, Zoltán 
76071830221SKővágó, Zoltán static void *qpa_audio_init(Audiodev *dev)
761b8e59f18Smalc {
7622c324b28SKővágó, Zoltán     paaudio *g;
7632c324b28SKővágó, Zoltán     AudiodevPaOptions *popts = &dev->u.pa;
7642c324b28SKővágó, Zoltán     const char *server;
7659d34e6d8SKővágó, Zoltán     PAConnection *c;
7669d34e6d8SKővágó, Zoltán 
7679d34e6d8SKővágó, Zoltán     assert(dev->driver == AUDIODEV_DRIVER_PA);
7682c324b28SKővágó, Zoltán 
7692c324b28SKővágó, Zoltán     if (!popts->has_server) {
770d175505bSGerd Hoffmann         char pidfile[64];
771d175505bSGerd Hoffmann         char *runtime;
772d175505bSGerd Hoffmann         struct stat st;
773d175505bSGerd Hoffmann 
774d175505bSGerd Hoffmann         runtime = getenv("XDG_RUNTIME_DIR");
775d175505bSGerd Hoffmann         if (!runtime) {
776d175505bSGerd Hoffmann             return NULL;
777d175505bSGerd Hoffmann         }
778d175505bSGerd Hoffmann         snprintf(pidfile, sizeof(pidfile), "%s/pulse/pid", runtime);
779d175505bSGerd Hoffmann         if (stat(pidfile, &st) != 0) {
780d175505bSGerd Hoffmann             return NULL;
781d175505bSGerd Hoffmann         }
782d175505bSGerd Hoffmann     }
783d175505bSGerd Hoffmann 
784baea032eSMartin Schrodt     if (!qpa_validate_per_direction_opts(dev, popts->in)) {
7859d34e6d8SKővágó, Zoltán         return NULL;
786baea032eSMartin Schrodt     }
787baea032eSMartin Schrodt     if (!qpa_validate_per_direction_opts(dev, popts->out)) {
7889d34e6d8SKővágó, Zoltán         return NULL;
789baea032eSMartin Schrodt     }
790baea032eSMartin Schrodt 
7919d34e6d8SKővágó, Zoltán     g = g_malloc0(sizeof(paaudio));
7929d34e6d8SKővágó, Zoltán     server = popts->has_server ? popts->server : NULL;
7939d34e6d8SKővágó, Zoltán 
7942c324b28SKővágó, Zoltán     g->dev = dev;
795ea9ebc2cSMarc-André Lureau 
7969d34e6d8SKővágó, Zoltán     QTAILQ_FOREACH(c, &pa_conns, list) {
7979d34e6d8SKővágó, Zoltán         if (server == NULL || c->server == NULL ?
7989d34e6d8SKővágó, Zoltán             server == c->server :
7999d34e6d8SKővágó, Zoltán             strcmp(server, c->server) == 0) {
8009d34e6d8SKővágó, Zoltán             g->conn = c;
801ea9ebc2cSMarc-André Lureau             break;
802ea9ebc2cSMarc-André Lureau         }
803ea9ebc2cSMarc-André Lureau     }
8049d34e6d8SKővágó, Zoltán     if (!g->conn) {
8059d34e6d8SKővágó, Zoltán         g->conn = qpa_conn_init(server);
806ea9ebc2cSMarc-André Lureau     }
8079d34e6d8SKővágó, Zoltán     if (!g->conn) {
8089d34e6d8SKővágó, Zoltán         g_free(g);
809ea9ebc2cSMarc-André Lureau         return NULL;
810b8e59f18Smalc     }
811b8e59f18Smalc 
8129d34e6d8SKővágó, Zoltán     ++g->conn->refcount;
8139d34e6d8SKővágó, Zoltán     return g;
8149d34e6d8SKővágó, Zoltán }
8159d34e6d8SKővágó, Zoltán 
8169d34e6d8SKővágó, Zoltán static void qpa_conn_fini(PAConnection *c)
8179d34e6d8SKővágó, Zoltán {
8189d34e6d8SKővágó, Zoltán     if (c->mainloop) {
8199d34e6d8SKővágó, Zoltán         pa_threaded_mainloop_stop(c->mainloop);
8209d34e6d8SKővágó, Zoltán     }
8219d34e6d8SKővágó, Zoltán 
8229d34e6d8SKővágó, Zoltán     if (c->context) {
8239d34e6d8SKővágó, Zoltán         pa_context_disconnect(c->context);
8249d34e6d8SKővágó, Zoltán         pa_context_unref(c->context);
8259d34e6d8SKővágó, Zoltán     }
8269d34e6d8SKővágó, Zoltán 
8279d34e6d8SKővágó, Zoltán     if (c->mainloop) {
8289d34e6d8SKővágó, Zoltán         pa_threaded_mainloop_free(c->mainloop);
8299d34e6d8SKővágó, Zoltán     }
8309d34e6d8SKővágó, Zoltán 
8319d34e6d8SKővágó, Zoltán     QTAILQ_REMOVE(&pa_conns, c, list);
8329d34e6d8SKővágó, Zoltán     g_free(c);
8339d34e6d8SKővágó, Zoltán }
8349d34e6d8SKővágó, Zoltán 
835b8e59f18Smalc static void qpa_audio_fini (void *opaque)
836b8e59f18Smalc {
837ea9ebc2cSMarc-André Lureau     paaudio *g = opaque;
8389d34e6d8SKővágó, Zoltán     PAConnection *c = g->conn;
839ea9ebc2cSMarc-André Lureau 
8409d34e6d8SKővágó, Zoltán     if (--c->refcount == 0) {
8419d34e6d8SKővágó, Zoltán         qpa_conn_fini(c);
842ea9ebc2cSMarc-André Lureau     }
843ea9ebc2cSMarc-André Lureau 
8449a644c4bSKővágó, Zoltán     g_free(g);
845b8e59f18Smalc }
846b8e59f18Smalc 
84735f4b58cSblueswir1 static struct audio_pcm_ops qpa_pcm_ops = {
8481dd3e4d1SJuan Quintela     .init_out = qpa_init_out,
8491dd3e4d1SJuan Quintela     .fini_out = qpa_fini_out,
85049ddd7e1SKővágó, Zoltán     .write    = qpa_write,
851337e8de6SKővágó, Zoltán     .get_buffer_out = qpa_get_buffer_out,
852337e8de6SKővágó, Zoltán     .put_buffer_out = qpa_write, /* pa handles it */
853571a8c52SKővágó, Zoltán     .volume_out = qpa_volume_out,
8541dd3e4d1SJuan Quintela 
8551dd3e4d1SJuan Quintela     .init_in  = qpa_init_in,
8561dd3e4d1SJuan Quintela     .fini_in  = qpa_fini_in,
85749ddd7e1SKővágó, Zoltán     .read     = qpa_read,
858337e8de6SKővágó, Zoltán     .get_buffer_in = qpa_get_buffer_in,
859337e8de6SKővágó, Zoltán     .put_buffer_in = qpa_put_buffer_in,
860571a8c52SKővágó, Zoltán     .volume_in = qpa_volume_in
861b8e59f18Smalc };
862b8e59f18Smalc 
863d3893a39SGerd Hoffmann static struct audio_driver pa_audio_driver = {
864bee37f32SJuan Quintela     .name           = "pa",
865bee37f32SJuan Quintela     .descr          = "http://www.pulseaudio.org/",
866bee37f32SJuan Quintela     .init           = qpa_audio_init,
867bee37f32SJuan Quintela     .fini           = qpa_audio_fini,
868bee37f32SJuan Quintela     .pcm_ops        = &qpa_pcm_ops,
8691a4ea1e3SMichael S. Tsirkin     .can_be_default = 1,
870bee37f32SJuan Quintela     .max_voices_out = INT_MAX,
871bee37f32SJuan Quintela     .max_voices_in  = INT_MAX,
872bee37f32SJuan Quintela     .voice_size_out = sizeof (PAVoiceOut),
8736e7a7f3dSMarc-André Lureau     .voice_size_in  = sizeof (PAVoiceIn),
874b8e59f18Smalc };
875d3893a39SGerd Hoffmann 
876d3893a39SGerd Hoffmann static void register_audio_pa(void)
877d3893a39SGerd Hoffmann {
878d3893a39SGerd Hoffmann     audio_driver_register(&pa_audio_driver);
879d3893a39SGerd Hoffmann }
880d3893a39SGerd Hoffmann type_init(register_audio_pa);
881