1b8e59f18Smalc /* public domain */ 20b8fa32fSMarkus Armbruster 36086a565SPeter Maydell #include "qemu/osdep.h" 40b8fa32fSMarkus Armbruster #include "qemu/module.h" 5b8e59f18Smalc #include "audio.h" 62c324b28SKővágó, Zoltán #include "qapi/opts-visitor.h" 7b8e59f18Smalc 8ea9ebc2cSMarc-André Lureau #include <pulse/pulseaudio.h> 9b8e59f18Smalc 10b8e59f18Smalc #define AUDIO_CAP "pulseaudio" 11b8e59f18Smalc #include "audio_int.h" 12b8e59f18Smalc #include "audio_pt_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; 33b8e59f18Smalc int done; 34b8e59f18Smalc int live; 35b8e59f18Smalc int decr; 36b8e59f18Smalc int rpos; 37ea9ebc2cSMarc-André Lureau pa_stream *stream; 38b8e59f18Smalc void *pcm_buf; 39b8e59f18Smalc struct audio_pt pt; 409a644c4bSKővágó, Zoltán paaudio *g; 412c324b28SKővágó, Zoltán int samples; 42b8e59f18Smalc } PAVoiceOut; 43b8e59f18Smalc 44b8e59f18Smalc typedef struct { 45b8e59f18Smalc HWVoiceIn hw; 46b8e59f18Smalc int done; 47b8e59f18Smalc int dead; 48b8e59f18Smalc int incr; 49b8e59f18Smalc int wpos; 50ea9ebc2cSMarc-André Lureau pa_stream *stream; 51b8e59f18Smalc void *pcm_buf; 52b8e59f18Smalc struct audio_pt pt; 53ea9ebc2cSMarc-André Lureau const void *read_data; 54ea9ebc2cSMarc-André Lureau size_t read_index, read_length; 559a644c4bSKővágó, Zoltán paaudio *g; 562c324b28SKővágó, Zoltán int samples; 57b8e59f18Smalc } PAVoiceIn; 58b8e59f18Smalc 599d34e6d8SKővágó, Zoltán static void qpa_conn_fini(PAConnection *c); 6049dd6d0dSKővágó, Zoltán 61b8e59f18Smalc static void GCC_FMT_ATTR (2, 3) qpa_logerr (int err, const char *fmt, ...) 62b8e59f18Smalc { 63b8e59f18Smalc va_list ap; 64b8e59f18Smalc 65b8e59f18Smalc va_start (ap, fmt); 66b8e59f18Smalc AUD_vlog (AUDIO_CAP, fmt, ap); 67b8e59f18Smalc va_end (ap); 68b8e59f18Smalc 69b8e59f18Smalc AUD_log (AUDIO_CAP, "Reason: %s\n", pa_strerror (err)); 70b8e59f18Smalc } 71b8e59f18Smalc 728f473dd1SGerd Hoffmann #ifndef PA_CONTEXT_IS_GOOD 738f473dd1SGerd Hoffmann static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) 748f473dd1SGerd Hoffmann { 758f473dd1SGerd Hoffmann return 768f473dd1SGerd Hoffmann x == PA_CONTEXT_CONNECTING || 778f473dd1SGerd Hoffmann x == PA_CONTEXT_AUTHORIZING || 788f473dd1SGerd Hoffmann x == PA_CONTEXT_SETTING_NAME || 798f473dd1SGerd Hoffmann x == PA_CONTEXT_READY; 808f473dd1SGerd Hoffmann } 818f473dd1SGerd Hoffmann #endif 828f473dd1SGerd Hoffmann 838f473dd1SGerd Hoffmann #ifndef PA_STREAM_IS_GOOD 848f473dd1SGerd Hoffmann static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) 858f473dd1SGerd Hoffmann { 868f473dd1SGerd Hoffmann return 878f473dd1SGerd Hoffmann x == PA_STREAM_CREATING || 888f473dd1SGerd Hoffmann x == PA_STREAM_READY; 898f473dd1SGerd Hoffmann } 908f473dd1SGerd Hoffmann #endif 918f473dd1SGerd Hoffmann 92ea9ebc2cSMarc-André Lureau #define CHECK_SUCCESS_GOTO(c, rerror, expression, label) \ 93ea9ebc2cSMarc-André Lureau do { \ 94ea9ebc2cSMarc-André Lureau if (!(expression)) { \ 95ea9ebc2cSMarc-André Lureau if (rerror) { \ 96ea9ebc2cSMarc-André Lureau *(rerror) = pa_context_errno ((c)->context); \ 97ea9ebc2cSMarc-André Lureau } \ 98ea9ebc2cSMarc-André Lureau goto label; \ 99ea9ebc2cSMarc-André Lureau } \ 1002562755eSEric Blake } while (0) 101ea9ebc2cSMarc-André Lureau 102ea9ebc2cSMarc-André Lureau #define CHECK_DEAD_GOTO(c, stream, rerror, label) \ 103ea9ebc2cSMarc-André Lureau do { \ 104ea9ebc2cSMarc-André Lureau if (!(c)->context || !PA_CONTEXT_IS_GOOD (pa_context_get_state((c)->context)) || \ 105ea9ebc2cSMarc-André Lureau !(stream) || !PA_STREAM_IS_GOOD (pa_stream_get_state ((stream)))) { \ 106ea9ebc2cSMarc-André Lureau if (((c)->context && pa_context_get_state ((c)->context) == PA_CONTEXT_FAILED) || \ 107ea9ebc2cSMarc-André Lureau ((stream) && pa_stream_get_state ((stream)) == PA_STREAM_FAILED)) { \ 108ea9ebc2cSMarc-André Lureau if (rerror) { \ 109ea9ebc2cSMarc-André Lureau *(rerror) = pa_context_errno ((c)->context); \ 110ea9ebc2cSMarc-André Lureau } \ 111ea9ebc2cSMarc-André Lureau } else { \ 112ea9ebc2cSMarc-André Lureau if (rerror) { \ 113ea9ebc2cSMarc-André Lureau *(rerror) = PA_ERR_BADSTATE; \ 114ea9ebc2cSMarc-André Lureau } \ 115ea9ebc2cSMarc-André Lureau } \ 116ea9ebc2cSMarc-André Lureau goto label; \ 117ea9ebc2cSMarc-André Lureau } \ 1182562755eSEric Blake } while (0) 119ea9ebc2cSMarc-André Lureau 120ea9ebc2cSMarc-André Lureau static int qpa_simple_read (PAVoiceIn *p, void *data, size_t length, int *rerror) 121ea9ebc2cSMarc-André Lureau { 1229d34e6d8SKővágó, Zoltán PAConnection *c = p->g->conn; 123ea9ebc2cSMarc-André Lureau 1249d34e6d8SKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 125ea9ebc2cSMarc-André Lureau 1269d34e6d8SKővágó, Zoltán CHECK_DEAD_GOTO(c, p->stream, rerror, unlock_and_fail); 127ea9ebc2cSMarc-André Lureau 128ea9ebc2cSMarc-André Lureau while (length > 0) { 129ea9ebc2cSMarc-André Lureau size_t l; 130ea9ebc2cSMarc-André Lureau 131ea9ebc2cSMarc-André Lureau while (!p->read_data) { 132ea9ebc2cSMarc-André Lureau int r; 133ea9ebc2cSMarc-André Lureau 134ea9ebc2cSMarc-André Lureau r = pa_stream_peek (p->stream, &p->read_data, &p->read_length); 1359d34e6d8SKővágó, Zoltán CHECK_SUCCESS_GOTO(c, rerror, r == 0, unlock_and_fail); 136ea9ebc2cSMarc-André Lureau 137ea9ebc2cSMarc-André Lureau if (!p->read_data) { 1389d34e6d8SKővágó, Zoltán pa_threaded_mainloop_wait(c->mainloop); 1399d34e6d8SKővágó, Zoltán CHECK_DEAD_GOTO(c, p->stream, rerror, unlock_and_fail); 140ea9ebc2cSMarc-André Lureau } else { 141ea9ebc2cSMarc-André Lureau p->read_index = 0; 142ea9ebc2cSMarc-André Lureau } 143ea9ebc2cSMarc-André Lureau } 144ea9ebc2cSMarc-André Lureau 145ea9ebc2cSMarc-André Lureau l = p->read_length < length ? p->read_length : length; 146ea9ebc2cSMarc-André Lureau memcpy (data, (const uint8_t *) p->read_data+p->read_index, l); 147ea9ebc2cSMarc-André Lureau 148ea9ebc2cSMarc-André Lureau data = (uint8_t *) data + l; 149ea9ebc2cSMarc-André Lureau length -= l; 150ea9ebc2cSMarc-André Lureau 151ea9ebc2cSMarc-André Lureau p->read_index += l; 152ea9ebc2cSMarc-André Lureau p->read_length -= l; 153ea9ebc2cSMarc-André Lureau 154ea9ebc2cSMarc-André Lureau if (!p->read_length) { 155ea9ebc2cSMarc-André Lureau int r; 156ea9ebc2cSMarc-André Lureau 157ea9ebc2cSMarc-André Lureau r = pa_stream_drop (p->stream); 158ea9ebc2cSMarc-André Lureau p->read_data = NULL; 159ea9ebc2cSMarc-André Lureau p->read_length = 0; 160ea9ebc2cSMarc-André Lureau p->read_index = 0; 161ea9ebc2cSMarc-André Lureau 1629d34e6d8SKővágó, Zoltán CHECK_SUCCESS_GOTO(c, rerror, r == 0, unlock_and_fail); 163ea9ebc2cSMarc-André Lureau } 164ea9ebc2cSMarc-André Lureau } 165ea9ebc2cSMarc-André Lureau 1669d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 167ea9ebc2cSMarc-André Lureau return 0; 168ea9ebc2cSMarc-André Lureau 169ea9ebc2cSMarc-André Lureau unlock_and_fail: 1709d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 171ea9ebc2cSMarc-André Lureau return -1; 172ea9ebc2cSMarc-André Lureau } 173ea9ebc2cSMarc-André Lureau 174ea9ebc2cSMarc-André Lureau static int qpa_simple_write (PAVoiceOut *p, const void *data, size_t length, int *rerror) 175ea9ebc2cSMarc-André Lureau { 1769d34e6d8SKővágó, Zoltán PAConnection *c = p->g->conn; 177ea9ebc2cSMarc-André Lureau 1789d34e6d8SKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 179ea9ebc2cSMarc-André Lureau 1809d34e6d8SKővágó, Zoltán CHECK_DEAD_GOTO(c, p->stream, rerror, unlock_and_fail); 181ea9ebc2cSMarc-André Lureau 182ea9ebc2cSMarc-André Lureau while (length > 0) { 183ea9ebc2cSMarc-André Lureau size_t l; 184ea9ebc2cSMarc-André Lureau int r; 185ea9ebc2cSMarc-André Lureau 186ea9ebc2cSMarc-André Lureau while (!(l = pa_stream_writable_size (p->stream))) { 1879d34e6d8SKővágó, Zoltán pa_threaded_mainloop_wait(c->mainloop); 1889d34e6d8SKővágó, Zoltán CHECK_DEAD_GOTO(c, p->stream, rerror, unlock_and_fail); 189ea9ebc2cSMarc-André Lureau } 190ea9ebc2cSMarc-André Lureau 1919d34e6d8SKővágó, Zoltán CHECK_SUCCESS_GOTO(c, rerror, l != (size_t) -1, unlock_and_fail); 192ea9ebc2cSMarc-André Lureau 193ea9ebc2cSMarc-André Lureau if (l > length) { 194ea9ebc2cSMarc-André Lureau l = length; 195ea9ebc2cSMarc-André Lureau } 196ea9ebc2cSMarc-André Lureau 197ea9ebc2cSMarc-André Lureau r = pa_stream_write (p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE); 1989d34e6d8SKővágó, Zoltán CHECK_SUCCESS_GOTO(c, rerror, r >= 0, unlock_and_fail); 199ea9ebc2cSMarc-André Lureau 200ea9ebc2cSMarc-André Lureau data = (const uint8_t *) data + l; 201ea9ebc2cSMarc-André Lureau length -= l; 202ea9ebc2cSMarc-André Lureau } 203ea9ebc2cSMarc-André Lureau 2049d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 205ea9ebc2cSMarc-André Lureau return 0; 206ea9ebc2cSMarc-André Lureau 207ea9ebc2cSMarc-André Lureau unlock_and_fail: 2089d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 209ea9ebc2cSMarc-André Lureau return -1; 210ea9ebc2cSMarc-André Lureau } 211ea9ebc2cSMarc-André Lureau 212b8e59f18Smalc static void *qpa_thread_out (void *arg) 213b8e59f18Smalc { 214b8e59f18Smalc PAVoiceOut *pa = arg; 215b8e59f18Smalc HWVoiceOut *hw = &pa->hw; 216b8e59f18Smalc 217470bcabdSAlistair Francis if (audio_pt_lock(&pa->pt, __func__)) { 218b8e59f18Smalc return NULL; 219b8e59f18Smalc } 220b8e59f18Smalc 221b8e59f18Smalc for (;;) { 222b8e59f18Smalc int decr, to_mix, rpos; 223b8e59f18Smalc 224b8e59f18Smalc for (;;) { 225b8e59f18Smalc if (pa->done) { 226b8e59f18Smalc goto exit; 227b8e59f18Smalc } 228b8e59f18Smalc 2296315633bSGerd Hoffmann if (pa->live > 0) { 230b8e59f18Smalc break; 231b8e59f18Smalc } 232b8e59f18Smalc 233470bcabdSAlistair Francis if (audio_pt_wait(&pa->pt, __func__)) { 234b8e59f18Smalc goto exit; 235b8e59f18Smalc } 236b8e59f18Smalc } 237b8e59f18Smalc 23858935915SKővágó, Zoltán decr = to_mix = MIN(pa->live, pa->samples >> 5); 2396315633bSGerd Hoffmann rpos = pa->rpos; 240b8e59f18Smalc 241470bcabdSAlistair Francis if (audio_pt_unlock(&pa->pt, __func__)) { 242b8e59f18Smalc return NULL; 243b8e59f18Smalc } 244b8e59f18Smalc 245b8e59f18Smalc while (to_mix) { 246b8e59f18Smalc int error; 24758935915SKővágó, Zoltán int chunk = MIN (to_mix, hw->samples - rpos); 2481ea879e5Smalc struct st_sample *src = hw->mix_buf + rpos; 249b8e59f18Smalc 250b8e59f18Smalc hw->clip (pa->pcm_buf, src, chunk); 251b8e59f18Smalc 252ea9ebc2cSMarc-André Lureau if (qpa_simple_write (pa, pa->pcm_buf, 253b8e59f18Smalc chunk << hw->info.shift, &error) < 0) { 254b8e59f18Smalc qpa_logerr (error, "pa_simple_write failed\n"); 255b8e59f18Smalc return NULL; 256b8e59f18Smalc } 257b8e59f18Smalc 258b8e59f18Smalc rpos = (rpos + chunk) % hw->samples; 259b8e59f18Smalc to_mix -= chunk; 260b8e59f18Smalc } 261b8e59f18Smalc 262470bcabdSAlistair Francis if (audio_pt_lock(&pa->pt, __func__)) { 263b8e59f18Smalc return NULL; 264b8e59f18Smalc } 265b8e59f18Smalc 266b8e59f18Smalc pa->rpos = rpos; 2676315633bSGerd Hoffmann pa->live -= decr; 268b8e59f18Smalc pa->decr += decr; 269b8e59f18Smalc } 270b8e59f18Smalc 271b8e59f18Smalc exit: 272470bcabdSAlistair Francis audio_pt_unlock(&pa->pt, __func__); 273b8e59f18Smalc return NULL; 274b8e59f18Smalc } 275b8e59f18Smalc 276bdff253cSmalc static int qpa_run_out (HWVoiceOut *hw, int live) 277b8e59f18Smalc { 278bdff253cSmalc int decr; 279b8e59f18Smalc PAVoiceOut *pa = (PAVoiceOut *) hw; 280b8e59f18Smalc 281470bcabdSAlistair Francis if (audio_pt_lock(&pa->pt, __func__)) { 282b8e59f18Smalc return 0; 283b8e59f18Smalc } 284b8e59f18Smalc 28558935915SKővágó, Zoltán decr = MIN (live, pa->decr); 286b8e59f18Smalc pa->decr -= decr; 287b8e59f18Smalc pa->live = live - decr; 288b8e59f18Smalc hw->rpos = pa->rpos; 289b8e59f18Smalc if (pa->live > 0) { 290470bcabdSAlistair Francis audio_pt_unlock_and_signal(&pa->pt, __func__); 291b8e59f18Smalc } 292b8e59f18Smalc else { 293470bcabdSAlistair Francis audio_pt_unlock(&pa->pt, __func__); 294b8e59f18Smalc } 295b8e59f18Smalc return decr; 296b8e59f18Smalc } 297b8e59f18Smalc 298b8e59f18Smalc static int qpa_write (SWVoiceOut *sw, void *buf, int len) 299b8e59f18Smalc { 300b8e59f18Smalc return audio_pcm_sw_write (sw, buf, len); 301b8e59f18Smalc } 302b8e59f18Smalc 303b8e59f18Smalc /* capture */ 304b8e59f18Smalc static void *qpa_thread_in (void *arg) 305b8e59f18Smalc { 306b8e59f18Smalc PAVoiceIn *pa = arg; 307b8e59f18Smalc HWVoiceIn *hw = &pa->hw; 308b8e59f18Smalc 309470bcabdSAlistair Francis if (audio_pt_lock(&pa->pt, __func__)) { 310b8e59f18Smalc return NULL; 311b8e59f18Smalc } 312b8e59f18Smalc 313b8e59f18Smalc for (;;) { 314b8e59f18Smalc int incr, to_grab, wpos; 315b8e59f18Smalc 316b8e59f18Smalc for (;;) { 317b8e59f18Smalc if (pa->done) { 318b8e59f18Smalc goto exit; 319b8e59f18Smalc } 320b8e59f18Smalc 3216315633bSGerd Hoffmann if (pa->dead > 0) { 322b8e59f18Smalc break; 323b8e59f18Smalc } 324b8e59f18Smalc 325470bcabdSAlistair Francis if (audio_pt_wait(&pa->pt, __func__)) { 326b8e59f18Smalc goto exit; 327b8e59f18Smalc } 328b8e59f18Smalc } 329b8e59f18Smalc 33058935915SKővágó, Zoltán incr = to_grab = MIN(pa->dead, pa->samples >> 5); 3316315633bSGerd Hoffmann wpos = pa->wpos; 332b8e59f18Smalc 333470bcabdSAlistair Francis if (audio_pt_unlock(&pa->pt, __func__)) { 334b8e59f18Smalc return NULL; 335b8e59f18Smalc } 336b8e59f18Smalc 337b8e59f18Smalc while (to_grab) { 338b8e59f18Smalc int error; 33958935915SKővágó, Zoltán int chunk = MIN (to_grab, hw->samples - wpos); 340b8e59f18Smalc void *buf = advance (pa->pcm_buf, wpos); 341b8e59f18Smalc 342ea9ebc2cSMarc-André Lureau if (qpa_simple_read (pa, buf, 343b8e59f18Smalc chunk << hw->info.shift, &error) < 0) { 344b8e59f18Smalc qpa_logerr (error, "pa_simple_read failed\n"); 345b8e59f18Smalc return NULL; 346b8e59f18Smalc } 347b8e59f18Smalc 34800e07679SMichael Walle hw->conv (hw->conv_buf + wpos, buf, chunk); 349b8e59f18Smalc wpos = (wpos + chunk) % hw->samples; 350b8e59f18Smalc to_grab -= chunk; 351b8e59f18Smalc } 352b8e59f18Smalc 353470bcabdSAlistair Francis if (audio_pt_lock(&pa->pt, __func__)) { 354b8e59f18Smalc return NULL; 355b8e59f18Smalc } 356b8e59f18Smalc 357b8e59f18Smalc pa->wpos = wpos; 358b8e59f18Smalc pa->dead -= incr; 359b8e59f18Smalc pa->incr += incr; 360b8e59f18Smalc } 361b8e59f18Smalc 362b8e59f18Smalc exit: 363470bcabdSAlistair Francis audio_pt_unlock(&pa->pt, __func__); 364b8e59f18Smalc return NULL; 365b8e59f18Smalc } 366b8e59f18Smalc 367b8e59f18Smalc static int qpa_run_in (HWVoiceIn *hw) 368b8e59f18Smalc { 369b8e59f18Smalc int live, incr, dead; 370b8e59f18Smalc PAVoiceIn *pa = (PAVoiceIn *) hw; 371b8e59f18Smalc 372470bcabdSAlistair Francis if (audio_pt_lock(&pa->pt, __func__)) { 373b8e59f18Smalc return 0; 374b8e59f18Smalc } 375b8e59f18Smalc 376b8e59f18Smalc live = audio_pcm_hw_get_live_in (hw); 377b8e59f18Smalc dead = hw->samples - live; 37858935915SKővágó, Zoltán incr = MIN (dead, pa->incr); 379b8e59f18Smalc pa->incr -= incr; 380b8e59f18Smalc pa->dead = dead - incr; 381b8e59f18Smalc hw->wpos = pa->wpos; 382b8e59f18Smalc if (pa->dead > 0) { 383470bcabdSAlistair Francis audio_pt_unlock_and_signal(&pa->pt, __func__); 384b8e59f18Smalc } 385b8e59f18Smalc else { 386470bcabdSAlistair Francis audio_pt_unlock(&pa->pt, __func__); 387b8e59f18Smalc } 388b8e59f18Smalc return incr; 389b8e59f18Smalc } 390b8e59f18Smalc 391b8e59f18Smalc static int qpa_read (SWVoiceIn *sw, void *buf, int len) 392b8e59f18Smalc { 393b8e59f18Smalc return audio_pcm_sw_read (sw, buf, len); 394b8e59f18Smalc } 395b8e59f18Smalc 39685bc5852SKővágó, Zoltán static pa_sample_format_t audfmt_to_pa (AudioFormat afmt, int endianness) 397b8e59f18Smalc { 398b8e59f18Smalc int format; 399b8e59f18Smalc 400b8e59f18Smalc switch (afmt) { 40185bc5852SKővágó, Zoltán case AUDIO_FORMAT_S8: 40285bc5852SKővágó, Zoltán case AUDIO_FORMAT_U8: 403b8e59f18Smalc format = PA_SAMPLE_U8; 404b8e59f18Smalc break; 40585bc5852SKővágó, Zoltán case AUDIO_FORMAT_S16: 40685bc5852SKővágó, Zoltán case AUDIO_FORMAT_U16: 407b8e59f18Smalc format = endianness ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE; 408b8e59f18Smalc break; 40985bc5852SKővágó, Zoltán case AUDIO_FORMAT_S32: 41085bc5852SKővágó, Zoltán case AUDIO_FORMAT_U32: 411b8e59f18Smalc format = endianness ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE; 412b8e59f18Smalc break; 413b8e59f18Smalc default: 414b8e59f18Smalc dolog ("Internal logic error: Bad audio format %d\n", afmt); 415b8e59f18Smalc format = PA_SAMPLE_U8; 416b8e59f18Smalc break; 417b8e59f18Smalc } 418b8e59f18Smalc return format; 419b8e59f18Smalc } 420b8e59f18Smalc 42185bc5852SKővágó, Zoltán static AudioFormat pa_to_audfmt (pa_sample_format_t fmt, int *endianness) 422b8e59f18Smalc { 423b8e59f18Smalc switch (fmt) { 424b8e59f18Smalc case PA_SAMPLE_U8: 42585bc5852SKővágó, Zoltán return AUDIO_FORMAT_U8; 426b8e59f18Smalc case PA_SAMPLE_S16BE: 427b8e59f18Smalc *endianness = 1; 42885bc5852SKővágó, Zoltán return AUDIO_FORMAT_S16; 429b8e59f18Smalc case PA_SAMPLE_S16LE: 430b8e59f18Smalc *endianness = 0; 43185bc5852SKővágó, Zoltán return AUDIO_FORMAT_S16; 432b8e59f18Smalc case PA_SAMPLE_S32BE: 433b8e59f18Smalc *endianness = 1; 43485bc5852SKővágó, Zoltán return AUDIO_FORMAT_S32; 435b8e59f18Smalc case PA_SAMPLE_S32LE: 436b8e59f18Smalc *endianness = 0; 43785bc5852SKővágó, Zoltán return AUDIO_FORMAT_S32; 438b8e59f18Smalc default: 439b8e59f18Smalc dolog ("Internal logic error: Bad pa_sample_format %d\n", fmt); 44085bc5852SKővágó, Zoltán return AUDIO_FORMAT_U8; 441b8e59f18Smalc } 442b8e59f18Smalc } 443b8e59f18Smalc 444ea9ebc2cSMarc-André Lureau static void context_state_cb (pa_context *c, void *userdata) 445ea9ebc2cSMarc-André Lureau { 4469d34e6d8SKővágó, Zoltán PAConnection *conn = userdata; 447ea9ebc2cSMarc-André Lureau 448ea9ebc2cSMarc-André Lureau switch (pa_context_get_state(c)) { 449ea9ebc2cSMarc-André Lureau case PA_CONTEXT_READY: 450ea9ebc2cSMarc-André Lureau case PA_CONTEXT_TERMINATED: 451ea9ebc2cSMarc-André Lureau case PA_CONTEXT_FAILED: 4529d34e6d8SKővágó, Zoltán pa_threaded_mainloop_signal(conn->mainloop, 0); 453ea9ebc2cSMarc-André Lureau break; 454ea9ebc2cSMarc-André Lureau 455ea9ebc2cSMarc-André Lureau case PA_CONTEXT_UNCONNECTED: 456ea9ebc2cSMarc-André Lureau case PA_CONTEXT_CONNECTING: 457ea9ebc2cSMarc-André Lureau case PA_CONTEXT_AUTHORIZING: 458ea9ebc2cSMarc-André Lureau case PA_CONTEXT_SETTING_NAME: 459ea9ebc2cSMarc-André Lureau break; 460ea9ebc2cSMarc-André Lureau } 461ea9ebc2cSMarc-André Lureau } 462ea9ebc2cSMarc-André Lureau 463ea9ebc2cSMarc-André Lureau static void stream_state_cb (pa_stream *s, void * userdata) 464ea9ebc2cSMarc-André Lureau { 4659d34e6d8SKővágó, Zoltán PAConnection *c = userdata; 466ea9ebc2cSMarc-André Lureau 467ea9ebc2cSMarc-André Lureau switch (pa_stream_get_state (s)) { 468ea9ebc2cSMarc-André Lureau 469ea9ebc2cSMarc-André Lureau case PA_STREAM_READY: 470ea9ebc2cSMarc-André Lureau case PA_STREAM_FAILED: 471ea9ebc2cSMarc-André Lureau case PA_STREAM_TERMINATED: 4729d34e6d8SKővágó, Zoltán pa_threaded_mainloop_signal(c->mainloop, 0); 473ea9ebc2cSMarc-André Lureau break; 474ea9ebc2cSMarc-André Lureau 475ea9ebc2cSMarc-André Lureau case PA_STREAM_UNCONNECTED: 476ea9ebc2cSMarc-André Lureau case PA_STREAM_CREATING: 477ea9ebc2cSMarc-André Lureau break; 478ea9ebc2cSMarc-André Lureau } 479ea9ebc2cSMarc-André Lureau } 480ea9ebc2cSMarc-André Lureau 481ea9ebc2cSMarc-André Lureau static void stream_request_cb (pa_stream *s, size_t length, void *userdata) 482ea9ebc2cSMarc-André Lureau { 4839d34e6d8SKővágó, Zoltán PAConnection *c = userdata; 484ea9ebc2cSMarc-André Lureau 4859d34e6d8SKővágó, Zoltán pa_threaded_mainloop_signal(c->mainloop, 0); 486ea9ebc2cSMarc-André Lureau } 487ea9ebc2cSMarc-André Lureau 488ea9ebc2cSMarc-André Lureau static pa_stream *qpa_simple_new ( 4899d34e6d8SKővágó, Zoltán PAConnection *c, 490ea9ebc2cSMarc-André Lureau const char *name, 491ea9ebc2cSMarc-André Lureau pa_stream_direction_t dir, 492ea9ebc2cSMarc-André Lureau const char *dev, 493ea9ebc2cSMarc-André Lureau const pa_sample_spec *ss, 494ea9ebc2cSMarc-André Lureau const pa_channel_map *map, 495ea9ebc2cSMarc-André Lureau const pa_buffer_attr *attr, 496ea9ebc2cSMarc-André Lureau int *rerror) 497ea9ebc2cSMarc-André Lureau { 498ea9ebc2cSMarc-André Lureau int r; 499ea9ebc2cSMarc-André Lureau pa_stream *stream; 5009d34e6d8SKővágó, Zoltán pa_stream_flags_t flags; 501ea9ebc2cSMarc-André Lureau 5029d34e6d8SKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 503ea9ebc2cSMarc-André Lureau 5049d34e6d8SKővágó, Zoltán stream = pa_stream_new(c->context, name, ss, map); 505ea9ebc2cSMarc-André Lureau if (!stream) { 506ea9ebc2cSMarc-André Lureau goto fail; 507ea9ebc2cSMarc-André Lureau } 508ea9ebc2cSMarc-André Lureau 5099d34e6d8SKővágó, Zoltán pa_stream_set_state_callback(stream, stream_state_cb, c); 5109d34e6d8SKővágó, Zoltán pa_stream_set_read_callback(stream, stream_request_cb, c); 5119d34e6d8SKővágó, Zoltán pa_stream_set_write_callback(stream, stream_request_cb, c); 5129d34e6d8SKővágó, Zoltán 5139d34e6d8SKővágó, Zoltán flags = 5149d34e6d8SKővágó, Zoltán PA_STREAM_INTERPOLATE_TIMING 515*10d5e750SKővágó, Zoltán | PA_STREAM_AUTO_TIMING_UPDATE 516*10d5e750SKővágó, Zoltán | PA_STREAM_EARLY_REQUESTS; 517ea9ebc2cSMarc-André Lureau 5188a435f74SKővágó, Zoltán if (dev) { 5198a435f74SKővágó, Zoltán /* don't move the stream if the user specified a sink/source */ 5208a435f74SKővágó, Zoltán flags |= PA_STREAM_DONT_MOVE; 5218a435f74SKővágó, Zoltán } 5228a435f74SKővágó, Zoltán 523ea9ebc2cSMarc-André Lureau if (dir == PA_STREAM_PLAYBACK) { 5249d34e6d8SKővágó, Zoltán r = pa_stream_connect_playback(stream, dev, attr, flags, NULL, NULL); 525ea9ebc2cSMarc-André Lureau } else { 5269d34e6d8SKővágó, Zoltán r = pa_stream_connect_record(stream, dev, attr, flags); 527ea9ebc2cSMarc-André Lureau } 528ea9ebc2cSMarc-André Lureau 529ea9ebc2cSMarc-André Lureau if (r < 0) { 530ea9ebc2cSMarc-André Lureau goto fail; 531ea9ebc2cSMarc-André Lureau } 532ea9ebc2cSMarc-André Lureau 5339d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 534ea9ebc2cSMarc-André Lureau 535ea9ebc2cSMarc-André Lureau return stream; 536ea9ebc2cSMarc-André Lureau 537ea9ebc2cSMarc-André Lureau fail: 5389d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 539ea9ebc2cSMarc-André Lureau 540ea9ebc2cSMarc-André Lureau if (stream) { 541ea9ebc2cSMarc-André Lureau pa_stream_unref (stream); 542ea9ebc2cSMarc-André Lureau } 543ea9ebc2cSMarc-André Lureau 5449d34e6d8SKővágó, Zoltán *rerror = pa_context_errno(c->context); 545ea9ebc2cSMarc-André Lureau 546ea9ebc2cSMarc-André Lureau return NULL; 547ea9ebc2cSMarc-André Lureau } 548ea9ebc2cSMarc-André Lureau 5495706db1dSKővágó, Zoltán static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, 5505706db1dSKővágó, Zoltán void *drv_opaque) 551b8e59f18Smalc { 552b8e59f18Smalc int error; 5539a644c4bSKővágó, Zoltán pa_sample_spec ss; 5549a644c4bSKővágó, Zoltán pa_buffer_attr ba; 5551ea879e5Smalc struct audsettings obt_as = *as; 556b8e59f18Smalc PAVoiceOut *pa = (PAVoiceOut *) hw; 5579a644c4bSKővágó, Zoltán paaudio *g = pa->g = drv_opaque; 5582c324b28SKővágó, Zoltán AudiodevPaOptions *popts = &g->dev->u.pa; 5592c324b28SKővágó, Zoltán AudiodevPaPerDirectionOptions *ppdo = popts->out; 5609d34e6d8SKővágó, Zoltán PAConnection *c = g->conn; 561b8e59f18Smalc 562b8e59f18Smalc ss.format = audfmt_to_pa (as->fmt, as->endianness); 563b8e59f18Smalc ss.channels = as->nchannels; 564b8e59f18Smalc ss.rate = as->freq; 565b8e59f18Smalc 566f6142777SMartin Schrodt ba.tlength = pa_usec_to_bytes(ppdo->latency, &ss); 567f6142777SMartin Schrodt ba.minreq = -1; 568e6d16fa4SGerd Hoffmann ba.maxlength = -1; 569e6d16fa4SGerd Hoffmann ba.prebuf = -1; 570e6d16fa4SGerd Hoffmann 571b8e59f18Smalc obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); 572b8e59f18Smalc 573ea9ebc2cSMarc-André Lureau pa->stream = qpa_simple_new ( 5749d34e6d8SKővágó, Zoltán c, 575b8e59f18Smalc "qemu", 576b8e59f18Smalc PA_STREAM_PLAYBACK, 5772c324b28SKővágó, Zoltán ppdo->has_name ? ppdo->name : NULL, 578b8e59f18Smalc &ss, 579b8e59f18Smalc NULL, /* channel map */ 580e6d16fa4SGerd Hoffmann &ba, /* buffering attributes */ 581b8e59f18Smalc &error 582b8e59f18Smalc ); 583ea9ebc2cSMarc-André Lureau if (!pa->stream) { 584b8e59f18Smalc qpa_logerr (error, "pa_simple_new for playback failed\n"); 585b8e59f18Smalc goto fail1; 586b8e59f18Smalc } 587b8e59f18Smalc 588b8e59f18Smalc audio_pcm_init_info (&hw->info, &obt_as); 5892c324b28SKővágó, Zoltán hw->samples = pa->samples = audio_buffer_samples( 590baea032eSMartin Schrodt qapi_AudiodevPaPerDirectionOptions_base(ppdo), 591baea032eSMartin Schrodt &obt_as, ppdo->buffer_length); 592470bcabdSAlistair Francis pa->pcm_buf = audio_calloc(__func__, hw->samples, 1 << hw->info.shift); 5936315633bSGerd Hoffmann pa->rpos = hw->rpos; 594b8e59f18Smalc if (!pa->pcm_buf) { 595b8e59f18Smalc dolog ("Could not allocate buffer (%d bytes)\n", 596b8e59f18Smalc hw->samples << hw->info.shift); 597b8e59f18Smalc goto fail2; 598b8e59f18Smalc } 599b8e59f18Smalc 600470bcabdSAlistair Francis if (audio_pt_init(&pa->pt, qpa_thread_out, hw, AUDIO_CAP, __func__)) { 601b8e59f18Smalc goto fail3; 602b8e59f18Smalc } 603b8e59f18Smalc 604b8e59f18Smalc return 0; 605b8e59f18Smalc 606b8e59f18Smalc fail3: 6077267c094SAnthony Liguori g_free (pa->pcm_buf); 608b8e59f18Smalc pa->pcm_buf = NULL; 609b8e59f18Smalc fail2: 610ea9ebc2cSMarc-André Lureau if (pa->stream) { 611ea9ebc2cSMarc-André Lureau pa_stream_unref (pa->stream); 612ea9ebc2cSMarc-André Lureau pa->stream = NULL; 613ea9ebc2cSMarc-André Lureau } 614b8e59f18Smalc fail1: 615b8e59f18Smalc return -1; 616b8e59f18Smalc } 617b8e59f18Smalc 6185706db1dSKővágó, Zoltán static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) 619b8e59f18Smalc { 620b8e59f18Smalc int error; 6219a644c4bSKővágó, Zoltán pa_sample_spec ss; 622ade10301SMartin Schrodt pa_buffer_attr ba; 6231ea879e5Smalc struct audsettings obt_as = *as; 624b8e59f18Smalc PAVoiceIn *pa = (PAVoiceIn *) hw; 6259a644c4bSKővágó, Zoltán paaudio *g = pa->g = drv_opaque; 6262c324b28SKővágó, Zoltán AudiodevPaOptions *popts = &g->dev->u.pa; 6272c324b28SKővágó, Zoltán AudiodevPaPerDirectionOptions *ppdo = popts->in; 6289d34e6d8SKővágó, Zoltán PAConnection *c = g->conn; 629b8e59f18Smalc 630b8e59f18Smalc ss.format = audfmt_to_pa (as->fmt, as->endianness); 631b8e59f18Smalc ss.channels = as->nchannels; 632b8e59f18Smalc ss.rate = as->freq; 633b8e59f18Smalc 634ade10301SMartin Schrodt ba.fragsize = pa_usec_to_bytes(ppdo->latency, &ss); 63558c15e52SMartin Schrodt ba.maxlength = pa_usec_to_bytes(ppdo->latency * 2, &ss); 636ade10301SMartin Schrodt ba.minreq = -1; 637ade10301SMartin Schrodt ba.prebuf = -1; 638ade10301SMartin Schrodt 639b8e59f18Smalc obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); 640b8e59f18Smalc 641ea9ebc2cSMarc-André Lureau pa->stream = qpa_simple_new ( 6429d34e6d8SKővágó, Zoltán c, 643b8e59f18Smalc "qemu", 644b8e59f18Smalc PA_STREAM_RECORD, 6452c324b28SKővágó, Zoltán ppdo->has_name ? ppdo->name : NULL, 646b8e59f18Smalc &ss, 647b8e59f18Smalc NULL, /* channel map */ 648ade10301SMartin Schrodt &ba, /* buffering attributes */ 649b8e59f18Smalc &error 650b8e59f18Smalc ); 651ea9ebc2cSMarc-André Lureau if (!pa->stream) { 652b8e59f18Smalc qpa_logerr (error, "pa_simple_new for capture failed\n"); 653b8e59f18Smalc goto fail1; 654b8e59f18Smalc } 655b8e59f18Smalc 656b8e59f18Smalc audio_pcm_init_info (&hw->info, &obt_as); 6572c324b28SKővágó, Zoltán hw->samples = pa->samples = audio_buffer_samples( 658baea032eSMartin Schrodt qapi_AudiodevPaPerDirectionOptions_base(ppdo), 659baea032eSMartin Schrodt &obt_as, ppdo->buffer_length); 660470bcabdSAlistair Francis pa->pcm_buf = audio_calloc(__func__, hw->samples, 1 << hw->info.shift); 6616315633bSGerd Hoffmann pa->wpos = hw->wpos; 662b8e59f18Smalc if (!pa->pcm_buf) { 663b8e59f18Smalc dolog ("Could not allocate buffer (%d bytes)\n", 664b8e59f18Smalc hw->samples << hw->info.shift); 665b8e59f18Smalc goto fail2; 666b8e59f18Smalc } 667b8e59f18Smalc 668470bcabdSAlistair Francis if (audio_pt_init(&pa->pt, qpa_thread_in, hw, AUDIO_CAP, __func__)) { 669b8e59f18Smalc goto fail3; 670b8e59f18Smalc } 671b8e59f18Smalc 672b8e59f18Smalc return 0; 673b8e59f18Smalc 674b8e59f18Smalc fail3: 6757267c094SAnthony Liguori g_free (pa->pcm_buf); 676b8e59f18Smalc pa->pcm_buf = NULL; 677b8e59f18Smalc fail2: 678ea9ebc2cSMarc-André Lureau if (pa->stream) { 679ea9ebc2cSMarc-André Lureau pa_stream_unref (pa->stream); 680ea9ebc2cSMarc-André Lureau pa->stream = NULL; 681ea9ebc2cSMarc-André Lureau } 682b8e59f18Smalc fail1: 683b8e59f18Smalc return -1; 684b8e59f18Smalc } 685b8e59f18Smalc 6868692bf7dSKővágó, Zoltán static void qpa_simple_disconnect(PAConnection *c, pa_stream *stream) 6878692bf7dSKővágó, Zoltán { 6888692bf7dSKővágó, Zoltán int err; 6898692bf7dSKővágó, Zoltán 6908692bf7dSKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 6918692bf7dSKővágó, Zoltán /* 6928692bf7dSKővágó, Zoltán * wait until actually connects. workaround pa bug #247 6938692bf7dSKővágó, Zoltán * https://gitlab.freedesktop.org/pulseaudio/pulseaudio/issues/247 6948692bf7dSKővágó, Zoltán */ 6958692bf7dSKővágó, Zoltán while (pa_stream_get_state(stream) == PA_STREAM_CREATING) { 6968692bf7dSKővágó, Zoltán pa_threaded_mainloop_wait(c->mainloop); 6978692bf7dSKővágó, Zoltán } 6988692bf7dSKővágó, Zoltán 6998692bf7dSKővágó, Zoltán err = pa_stream_disconnect(stream); 7008692bf7dSKővágó, Zoltán if (err != 0) { 7018692bf7dSKővágó, Zoltán dolog("Failed to disconnect! err=%d\n", err); 7028692bf7dSKővágó, Zoltán } 7038692bf7dSKővágó, Zoltán pa_stream_unref(stream); 7048692bf7dSKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 7058692bf7dSKővágó, Zoltán } 7068692bf7dSKővágó, Zoltán 707b8e59f18Smalc static void qpa_fini_out (HWVoiceOut *hw) 708b8e59f18Smalc { 709b8e59f18Smalc void *ret; 710b8e59f18Smalc PAVoiceOut *pa = (PAVoiceOut *) hw; 711b8e59f18Smalc 712470bcabdSAlistair Francis audio_pt_lock(&pa->pt, __func__); 713b8e59f18Smalc pa->done = 1; 714470bcabdSAlistair Francis audio_pt_unlock_and_signal(&pa->pt, __func__); 715470bcabdSAlistair Francis audio_pt_join(&pa->pt, &ret, __func__); 716b8e59f18Smalc 717ea9ebc2cSMarc-André Lureau if (pa->stream) { 7188692bf7dSKővágó, Zoltán qpa_simple_disconnect(pa->g->conn, pa->stream); 719ea9ebc2cSMarc-André Lureau pa->stream = NULL; 720b8e59f18Smalc } 721b8e59f18Smalc 722470bcabdSAlistair Francis audio_pt_fini(&pa->pt, __func__); 7237267c094SAnthony Liguori g_free (pa->pcm_buf); 724b8e59f18Smalc pa->pcm_buf = NULL; 725b8e59f18Smalc } 726b8e59f18Smalc 727b8e59f18Smalc static void qpa_fini_in (HWVoiceIn *hw) 728b8e59f18Smalc { 729b8e59f18Smalc void *ret; 730b8e59f18Smalc PAVoiceIn *pa = (PAVoiceIn *) hw; 731b8e59f18Smalc 732470bcabdSAlistair Francis audio_pt_lock(&pa->pt, __func__); 733b8e59f18Smalc pa->done = 1; 734470bcabdSAlistair Francis audio_pt_unlock_and_signal(&pa->pt, __func__); 735470bcabdSAlistair Francis audio_pt_join(&pa->pt, &ret, __func__); 736b8e59f18Smalc 737ea9ebc2cSMarc-André Lureau if (pa->stream) { 7388692bf7dSKővágó, Zoltán qpa_simple_disconnect(pa->g->conn, pa->stream); 739ea9ebc2cSMarc-André Lureau pa->stream = NULL; 740b8e59f18Smalc } 741b8e59f18Smalc 742470bcabdSAlistair Francis audio_pt_fini(&pa->pt, __func__); 7437267c094SAnthony Liguori g_free (pa->pcm_buf); 744b8e59f18Smalc pa->pcm_buf = NULL; 745b8e59f18Smalc } 746b8e59f18Smalc 747b8e59f18Smalc static int qpa_ctl_out (HWVoiceOut *hw, int cmd, ...) 748b8e59f18Smalc { 7496e7a7f3dSMarc-André Lureau PAVoiceOut *pa = (PAVoiceOut *) hw; 7506e7a7f3dSMarc-André Lureau pa_operation *op; 7516e7a7f3dSMarc-André Lureau pa_cvolume v; 7529d34e6d8SKővágó, Zoltán PAConnection *c = pa->g->conn; 7536e7a7f3dSMarc-André Lureau 7548f473dd1SGerd Hoffmann #ifdef PA_CHECK_VERSION /* macro is present in 0.9.16+ */ 7558f473dd1SGerd Hoffmann pa_cvolume_init (&v); /* function is present in 0.9.13+ */ 7568f473dd1SGerd Hoffmann #endif 7576e7a7f3dSMarc-André Lureau 7586e7a7f3dSMarc-André Lureau switch (cmd) { 7596e7a7f3dSMarc-André Lureau case VOICE_VOLUME: 7606e7a7f3dSMarc-André Lureau { 7616e7a7f3dSMarc-André Lureau SWVoiceOut *sw; 7626e7a7f3dSMarc-André Lureau va_list ap; 7636e7a7f3dSMarc-André Lureau 7646e7a7f3dSMarc-André Lureau va_start (ap, cmd); 7656e7a7f3dSMarc-André Lureau sw = va_arg (ap, SWVoiceOut *); 7666e7a7f3dSMarc-André Lureau va_end (ap); 7676e7a7f3dSMarc-André Lureau 7686e7a7f3dSMarc-André Lureau v.channels = 2; 7696e7a7f3dSMarc-André Lureau v.values[0] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.l) / UINT32_MAX; 7706e7a7f3dSMarc-André Lureau v.values[1] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.r) / UINT32_MAX; 7716e7a7f3dSMarc-André Lureau 7729d34e6d8SKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 7736e7a7f3dSMarc-André Lureau 7749d34e6d8SKővágó, Zoltán op = pa_context_set_sink_input_volume(c->context, 7756e7a7f3dSMarc-André Lureau pa_stream_get_index (pa->stream), 7766e7a7f3dSMarc-André Lureau &v, NULL, NULL); 7779d34e6d8SKővágó, Zoltán if (!op) { 7789d34e6d8SKővágó, Zoltán qpa_logerr(pa_context_errno(c->context), 7796e7a7f3dSMarc-André Lureau "set_sink_input_volume() failed\n"); 7809d34e6d8SKővágó, Zoltán } else { 7816e7a7f3dSMarc-André Lureau pa_operation_unref(op); 7829d34e6d8SKővágó, Zoltán } 7836e7a7f3dSMarc-André Lureau 7849d34e6d8SKővágó, Zoltán op = pa_context_set_sink_input_mute(c->context, 7856e7a7f3dSMarc-André Lureau pa_stream_get_index (pa->stream), 7866e7a7f3dSMarc-André Lureau sw->vol.mute, NULL, NULL); 7876e7a7f3dSMarc-André Lureau if (!op) { 7889d34e6d8SKővágó, Zoltán qpa_logerr(pa_context_errno(c->context), 7896e7a7f3dSMarc-André Lureau "set_sink_input_mute() failed\n"); 7906e7a7f3dSMarc-André Lureau } else { 7916e7a7f3dSMarc-André Lureau pa_operation_unref(op); 7926e7a7f3dSMarc-André Lureau } 7936e7a7f3dSMarc-André Lureau 7949d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 7956e7a7f3dSMarc-André Lureau } 7966e7a7f3dSMarc-André Lureau } 797b8e59f18Smalc return 0; 798b8e59f18Smalc } 799b8e59f18Smalc 800b8e59f18Smalc static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) 801b8e59f18Smalc { 8026e7a7f3dSMarc-André Lureau PAVoiceIn *pa = (PAVoiceIn *) hw; 8036e7a7f3dSMarc-André Lureau pa_operation *op; 8046e7a7f3dSMarc-André Lureau pa_cvolume v; 8059d34e6d8SKővágó, Zoltán PAConnection *c = pa->g->conn; 8066e7a7f3dSMarc-André Lureau 8078f473dd1SGerd Hoffmann #ifdef PA_CHECK_VERSION 8086e7a7f3dSMarc-André Lureau pa_cvolume_init (&v); 8098f473dd1SGerd Hoffmann #endif 8106e7a7f3dSMarc-André Lureau 8116e7a7f3dSMarc-André Lureau switch (cmd) { 8126e7a7f3dSMarc-André Lureau case VOICE_VOLUME: 8136e7a7f3dSMarc-André Lureau { 8146e7a7f3dSMarc-André Lureau SWVoiceIn *sw; 8156e7a7f3dSMarc-André Lureau va_list ap; 8166e7a7f3dSMarc-André Lureau 8176e7a7f3dSMarc-André Lureau va_start (ap, cmd); 8186e7a7f3dSMarc-André Lureau sw = va_arg (ap, SWVoiceIn *); 8196e7a7f3dSMarc-André Lureau va_end (ap); 8206e7a7f3dSMarc-André Lureau 8216e7a7f3dSMarc-André Lureau v.channels = 2; 8226e7a7f3dSMarc-André Lureau v.values[0] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.l) / UINT32_MAX; 8236e7a7f3dSMarc-André Lureau v.values[1] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.r) / UINT32_MAX; 8246e7a7f3dSMarc-André Lureau 8259d34e6d8SKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 8266e7a7f3dSMarc-André Lureau 8279d34e6d8SKővágó, Zoltán op = pa_context_set_source_output_volume(c->context, 828e58ff62dSPeter Krempa pa_stream_get_index(pa->stream), 8296e7a7f3dSMarc-André Lureau &v, NULL, NULL); 8306e7a7f3dSMarc-André Lureau if (!op) { 8319d34e6d8SKővágó, Zoltán qpa_logerr(pa_context_errno(c->context), 832e58ff62dSPeter Krempa "set_source_output_volume() failed\n"); 8336e7a7f3dSMarc-André Lureau } else { 8346e7a7f3dSMarc-André Lureau pa_operation_unref(op); 8356e7a7f3dSMarc-André Lureau } 8366e7a7f3dSMarc-André Lureau 8379d34e6d8SKővágó, Zoltán op = pa_context_set_source_output_mute(c->context, 8386e7a7f3dSMarc-André Lureau pa_stream_get_index (pa->stream), 8396e7a7f3dSMarc-André Lureau sw->vol.mute, NULL, NULL); 8406e7a7f3dSMarc-André Lureau if (!op) { 8419d34e6d8SKővágó, Zoltán qpa_logerr(pa_context_errno(c->context), 842e58ff62dSPeter Krempa "set_source_output_mute() failed\n"); 8436e7a7f3dSMarc-André Lureau } else { 8446e7a7f3dSMarc-André Lureau pa_operation_unref (op); 8456e7a7f3dSMarc-André Lureau } 8466e7a7f3dSMarc-André Lureau 8479d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 8486e7a7f3dSMarc-André Lureau } 8496e7a7f3dSMarc-André Lureau } 850b8e59f18Smalc return 0; 851b8e59f18Smalc } 852b8e59f18Smalc 853baea032eSMartin Schrodt static int qpa_validate_per_direction_opts(Audiodev *dev, 854baea032eSMartin Schrodt AudiodevPaPerDirectionOptions *pdo) 855baea032eSMartin Schrodt { 856baea032eSMartin Schrodt if (!pdo->has_buffer_length) { 857baea032eSMartin Schrodt pdo->has_buffer_length = true; 858baea032eSMartin Schrodt pdo->buffer_length = 46440; 859baea032eSMartin Schrodt } 860f6142777SMartin Schrodt if (!pdo->has_latency) { 861f6142777SMartin Schrodt pdo->has_latency = true; 862f6142777SMartin Schrodt pdo->latency = 15000; 863f6142777SMartin Schrodt } 864baea032eSMartin Schrodt return 1; 865baea032eSMartin Schrodt } 866baea032eSMartin Schrodt 8679d34e6d8SKővágó, Zoltán /* common */ 8689d34e6d8SKővágó, Zoltán static void *qpa_conn_init(const char *server) 8699d34e6d8SKővágó, Zoltán { 8709d34e6d8SKővágó, Zoltán PAConnection *c = g_malloc0(sizeof(PAConnection)); 8719d34e6d8SKővágó, Zoltán QTAILQ_INSERT_TAIL(&pa_conns, c, list); 8729d34e6d8SKővágó, Zoltán 8739d34e6d8SKővágó, Zoltán c->mainloop = pa_threaded_mainloop_new(); 8749d34e6d8SKővágó, Zoltán if (!c->mainloop) { 8759d34e6d8SKővágó, Zoltán goto fail; 8769d34e6d8SKővágó, Zoltán } 8779d34e6d8SKővágó, Zoltán 8789d34e6d8SKővágó, Zoltán c->context = pa_context_new(pa_threaded_mainloop_get_api(c->mainloop), 8799d34e6d8SKővágó, Zoltán server); 8809d34e6d8SKővágó, Zoltán if (!c->context) { 8819d34e6d8SKővágó, Zoltán goto fail; 8829d34e6d8SKővágó, Zoltán } 8839d34e6d8SKővágó, Zoltán 8849d34e6d8SKővágó, Zoltán pa_context_set_state_callback(c->context, context_state_cb, c); 8859d34e6d8SKővágó, Zoltán 8869d34e6d8SKővágó, Zoltán if (pa_context_connect(c->context, server, 0, NULL) < 0) { 8879d34e6d8SKővágó, Zoltán qpa_logerr(pa_context_errno(c->context), 8889d34e6d8SKővágó, Zoltán "pa_context_connect() failed\n"); 8899d34e6d8SKővágó, Zoltán goto fail; 8909d34e6d8SKővágó, Zoltán } 8919d34e6d8SKővágó, Zoltán 8929d34e6d8SKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 8939d34e6d8SKővágó, Zoltán 8949d34e6d8SKővágó, Zoltán if (pa_threaded_mainloop_start(c->mainloop) < 0) { 8959d34e6d8SKővágó, Zoltán goto unlock_and_fail; 8969d34e6d8SKővágó, Zoltán } 8979d34e6d8SKővágó, Zoltán 8989d34e6d8SKővágó, Zoltán for (;;) { 8999d34e6d8SKővágó, Zoltán pa_context_state_t state; 9009d34e6d8SKővágó, Zoltán 9019d34e6d8SKővágó, Zoltán state = pa_context_get_state(c->context); 9029d34e6d8SKővágó, Zoltán 9039d34e6d8SKővágó, Zoltán if (state == PA_CONTEXT_READY) { 9049d34e6d8SKővágó, Zoltán break; 9059d34e6d8SKővágó, Zoltán } 9069d34e6d8SKővágó, Zoltán 9079d34e6d8SKővágó, Zoltán if (!PA_CONTEXT_IS_GOOD(state)) { 9089d34e6d8SKővágó, Zoltán qpa_logerr(pa_context_errno(c->context), 9099d34e6d8SKővágó, Zoltán "Wrong context state\n"); 9109d34e6d8SKővágó, Zoltán goto unlock_and_fail; 9119d34e6d8SKővágó, Zoltán } 9129d34e6d8SKővágó, Zoltán 9139d34e6d8SKővágó, Zoltán /* Wait until the context is ready */ 9149d34e6d8SKővágó, Zoltán pa_threaded_mainloop_wait(c->mainloop); 9159d34e6d8SKővágó, Zoltán } 9169d34e6d8SKővágó, Zoltán 9179d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 9189d34e6d8SKővágó, Zoltán return c; 9199d34e6d8SKővágó, Zoltán 9209d34e6d8SKővágó, Zoltán unlock_and_fail: 9219d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 9229d34e6d8SKővágó, Zoltán fail: 9239d34e6d8SKővágó, Zoltán AUD_log (AUDIO_CAP, "Failed to initialize PA context"); 9249d34e6d8SKővágó, Zoltán qpa_conn_fini(c); 9259d34e6d8SKővágó, Zoltán return NULL; 9269d34e6d8SKővágó, Zoltán } 9279d34e6d8SKővágó, Zoltán 92871830221SKővágó, Zoltán static void *qpa_audio_init(Audiodev *dev) 929b8e59f18Smalc { 9302c324b28SKővágó, Zoltán paaudio *g; 9312c324b28SKővágó, Zoltán AudiodevPaOptions *popts = &dev->u.pa; 9322c324b28SKővágó, Zoltán const char *server; 9339d34e6d8SKővágó, Zoltán PAConnection *c; 9349d34e6d8SKővágó, Zoltán 9359d34e6d8SKővágó, Zoltán assert(dev->driver == AUDIODEV_DRIVER_PA); 9362c324b28SKővágó, Zoltán 9372c324b28SKővágó, Zoltán if (!popts->has_server) { 938d175505bSGerd Hoffmann char pidfile[64]; 939d175505bSGerd Hoffmann char *runtime; 940d175505bSGerd Hoffmann struct stat st; 941d175505bSGerd Hoffmann 942d175505bSGerd Hoffmann runtime = getenv("XDG_RUNTIME_DIR"); 943d175505bSGerd Hoffmann if (!runtime) { 944d175505bSGerd Hoffmann return NULL; 945d175505bSGerd Hoffmann } 946d175505bSGerd Hoffmann snprintf(pidfile, sizeof(pidfile), "%s/pulse/pid", runtime); 947d175505bSGerd Hoffmann if (stat(pidfile, &st) != 0) { 948d175505bSGerd Hoffmann return NULL; 949d175505bSGerd Hoffmann } 950d175505bSGerd Hoffmann } 951d175505bSGerd Hoffmann 952baea032eSMartin Schrodt if (!qpa_validate_per_direction_opts(dev, popts->in)) { 9539d34e6d8SKővágó, Zoltán return NULL; 954baea032eSMartin Schrodt } 955baea032eSMartin Schrodt if (!qpa_validate_per_direction_opts(dev, popts->out)) { 9569d34e6d8SKővágó, Zoltán return NULL; 957baea032eSMartin Schrodt } 958baea032eSMartin Schrodt 9599d34e6d8SKővágó, Zoltán g = g_malloc0(sizeof(paaudio)); 9609d34e6d8SKővágó, Zoltán server = popts->has_server ? popts->server : NULL; 9619d34e6d8SKővágó, Zoltán 9622c324b28SKővágó, Zoltán g->dev = dev; 963ea9ebc2cSMarc-André Lureau 9649d34e6d8SKővágó, Zoltán QTAILQ_FOREACH(c, &pa_conns, list) { 9659d34e6d8SKővágó, Zoltán if (server == NULL || c->server == NULL ? 9669d34e6d8SKővágó, Zoltán server == c->server : 9679d34e6d8SKővágó, Zoltán strcmp(server, c->server) == 0) { 9689d34e6d8SKővágó, Zoltán g->conn = c; 969ea9ebc2cSMarc-André Lureau break; 970ea9ebc2cSMarc-André Lureau } 971ea9ebc2cSMarc-André Lureau } 9729d34e6d8SKővágó, Zoltán if (!g->conn) { 9739d34e6d8SKővágó, Zoltán g->conn = qpa_conn_init(server); 974ea9ebc2cSMarc-André Lureau } 9759d34e6d8SKővágó, Zoltán if (!g->conn) { 9769d34e6d8SKővágó, Zoltán g_free(g); 977ea9ebc2cSMarc-André Lureau return NULL; 978b8e59f18Smalc } 979b8e59f18Smalc 9809d34e6d8SKővágó, Zoltán ++g->conn->refcount; 9819d34e6d8SKővágó, Zoltán return g; 9829d34e6d8SKővágó, Zoltán } 9839d34e6d8SKővágó, Zoltán 9849d34e6d8SKővágó, Zoltán static void qpa_conn_fini(PAConnection *c) 9859d34e6d8SKővágó, Zoltán { 9869d34e6d8SKővágó, Zoltán if (c->mainloop) { 9879d34e6d8SKővágó, Zoltán pa_threaded_mainloop_stop(c->mainloop); 9889d34e6d8SKővágó, Zoltán } 9899d34e6d8SKővágó, Zoltán 9909d34e6d8SKővágó, Zoltán if (c->context) { 9919d34e6d8SKővágó, Zoltán pa_context_disconnect(c->context); 9929d34e6d8SKővágó, Zoltán pa_context_unref(c->context); 9939d34e6d8SKővágó, Zoltán } 9949d34e6d8SKővágó, Zoltán 9959d34e6d8SKővágó, Zoltán if (c->mainloop) { 9969d34e6d8SKővágó, Zoltán pa_threaded_mainloop_free(c->mainloop); 9979d34e6d8SKővágó, Zoltán } 9989d34e6d8SKővágó, Zoltán 9999d34e6d8SKővágó, Zoltán QTAILQ_REMOVE(&pa_conns, c, list); 10009d34e6d8SKővágó, Zoltán g_free(c); 10019d34e6d8SKővágó, Zoltán } 10029d34e6d8SKővágó, Zoltán 1003b8e59f18Smalc static void qpa_audio_fini (void *opaque) 1004b8e59f18Smalc { 1005ea9ebc2cSMarc-André Lureau paaudio *g = opaque; 10069d34e6d8SKővágó, Zoltán PAConnection *c = g->conn; 1007ea9ebc2cSMarc-André Lureau 10089d34e6d8SKővágó, Zoltán if (--c->refcount == 0) { 10099d34e6d8SKővágó, Zoltán qpa_conn_fini(c); 1010ea9ebc2cSMarc-André Lureau } 1011ea9ebc2cSMarc-André Lureau 10129a644c4bSKővágó, Zoltán g_free(g); 1013b8e59f18Smalc } 1014b8e59f18Smalc 101535f4b58cSblueswir1 static struct audio_pcm_ops qpa_pcm_ops = { 10161dd3e4d1SJuan Quintela .init_out = qpa_init_out, 10171dd3e4d1SJuan Quintela .fini_out = qpa_fini_out, 10181dd3e4d1SJuan Quintela .run_out = qpa_run_out, 10191dd3e4d1SJuan Quintela .write = qpa_write, 10201dd3e4d1SJuan Quintela .ctl_out = qpa_ctl_out, 10211dd3e4d1SJuan Quintela 10221dd3e4d1SJuan Quintela .init_in = qpa_init_in, 10231dd3e4d1SJuan Quintela .fini_in = qpa_fini_in, 10241dd3e4d1SJuan Quintela .run_in = qpa_run_in, 10251dd3e4d1SJuan Quintela .read = qpa_read, 10261dd3e4d1SJuan Quintela .ctl_in = qpa_ctl_in 1027b8e59f18Smalc }; 1028b8e59f18Smalc 1029d3893a39SGerd Hoffmann static struct audio_driver pa_audio_driver = { 1030bee37f32SJuan Quintela .name = "pa", 1031bee37f32SJuan Quintela .descr = "http://www.pulseaudio.org/", 1032bee37f32SJuan Quintela .init = qpa_audio_init, 1033bee37f32SJuan Quintela .fini = qpa_audio_fini, 1034bee37f32SJuan Quintela .pcm_ops = &qpa_pcm_ops, 10351a4ea1e3SMichael S. Tsirkin .can_be_default = 1, 1036bee37f32SJuan Quintela .max_voices_out = INT_MAX, 1037bee37f32SJuan Quintela .max_voices_in = INT_MAX, 1038bee37f32SJuan Quintela .voice_size_out = sizeof (PAVoiceOut), 10396e7a7f3dSMarc-André Lureau .voice_size_in = sizeof (PAVoiceIn), 10406e7a7f3dSMarc-André Lureau .ctl_caps = VOICE_VOLUME_CAP 1041b8e59f18Smalc }; 1042d3893a39SGerd Hoffmann 1043d3893a39SGerd Hoffmann static void register_audio_pa(void) 1044d3893a39SGerd Hoffmann { 1045d3893a39SGerd Hoffmann audio_driver_register(&pa_audio_driver); 1046d3893a39SGerd Hoffmann } 1047d3893a39SGerd Hoffmann type_init(register_audio_pa); 1048