1b8e59f18Smalc /* public domain */ 2*0b8fa32fSMarkus Armbruster 36086a565SPeter Maydell #include "qemu/osdep.h" 4*0b8fa32fSMarkus 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 14b8e59f18Smalc typedef struct { 152c324b28SKővágó, Zoltán Audiodev *dev; 169a644c4bSKővágó, Zoltán pa_threaded_mainloop *mainloop; 179a644c4bSKővágó, Zoltán pa_context *context; 189a644c4bSKővágó, Zoltán } paaudio; 199a644c4bSKővágó, Zoltán 209a644c4bSKővágó, Zoltán typedef struct { 21b8e59f18Smalc HWVoiceOut hw; 22b8e59f18Smalc int done; 23b8e59f18Smalc int live; 24b8e59f18Smalc int decr; 25b8e59f18Smalc int rpos; 26ea9ebc2cSMarc-André Lureau pa_stream *stream; 27b8e59f18Smalc void *pcm_buf; 28b8e59f18Smalc struct audio_pt pt; 299a644c4bSKővágó, Zoltán paaudio *g; 302c324b28SKővágó, Zoltán int samples; 31b8e59f18Smalc } PAVoiceOut; 32b8e59f18Smalc 33b8e59f18Smalc typedef struct { 34b8e59f18Smalc HWVoiceIn hw; 35b8e59f18Smalc int done; 36b8e59f18Smalc int dead; 37b8e59f18Smalc int incr; 38b8e59f18Smalc int wpos; 39ea9ebc2cSMarc-André Lureau pa_stream *stream; 40b8e59f18Smalc void *pcm_buf; 41b8e59f18Smalc struct audio_pt pt; 42ea9ebc2cSMarc-André Lureau const void *read_data; 43ea9ebc2cSMarc-André Lureau size_t read_index, read_length; 449a644c4bSKővágó, Zoltán paaudio *g; 452c324b28SKővágó, Zoltán int samples; 46b8e59f18Smalc } PAVoiceIn; 47b8e59f18Smalc 4849dd6d0dSKővágó, Zoltán static void qpa_audio_fini(void *opaque); 4949dd6d0dSKővágó, Zoltán 50b8e59f18Smalc static void GCC_FMT_ATTR (2, 3) qpa_logerr (int err, const char *fmt, ...) 51b8e59f18Smalc { 52b8e59f18Smalc va_list ap; 53b8e59f18Smalc 54b8e59f18Smalc va_start (ap, fmt); 55b8e59f18Smalc AUD_vlog (AUDIO_CAP, fmt, ap); 56b8e59f18Smalc va_end (ap); 57b8e59f18Smalc 58b8e59f18Smalc AUD_log (AUDIO_CAP, "Reason: %s\n", pa_strerror (err)); 59b8e59f18Smalc } 60b8e59f18Smalc 618f473dd1SGerd Hoffmann #ifndef PA_CONTEXT_IS_GOOD 628f473dd1SGerd Hoffmann static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) 638f473dd1SGerd Hoffmann { 648f473dd1SGerd Hoffmann return 658f473dd1SGerd Hoffmann x == PA_CONTEXT_CONNECTING || 668f473dd1SGerd Hoffmann x == PA_CONTEXT_AUTHORIZING || 678f473dd1SGerd Hoffmann x == PA_CONTEXT_SETTING_NAME || 688f473dd1SGerd Hoffmann x == PA_CONTEXT_READY; 698f473dd1SGerd Hoffmann } 708f473dd1SGerd Hoffmann #endif 718f473dd1SGerd Hoffmann 728f473dd1SGerd Hoffmann #ifndef PA_STREAM_IS_GOOD 738f473dd1SGerd Hoffmann static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) 748f473dd1SGerd Hoffmann { 758f473dd1SGerd Hoffmann return 768f473dd1SGerd Hoffmann x == PA_STREAM_CREATING || 778f473dd1SGerd Hoffmann x == PA_STREAM_READY; 788f473dd1SGerd Hoffmann } 798f473dd1SGerd Hoffmann #endif 808f473dd1SGerd Hoffmann 81ea9ebc2cSMarc-André Lureau #define CHECK_SUCCESS_GOTO(c, rerror, expression, label) \ 82ea9ebc2cSMarc-André Lureau do { \ 83ea9ebc2cSMarc-André Lureau if (!(expression)) { \ 84ea9ebc2cSMarc-André Lureau if (rerror) { \ 85ea9ebc2cSMarc-André Lureau *(rerror) = pa_context_errno ((c)->context); \ 86ea9ebc2cSMarc-André Lureau } \ 87ea9ebc2cSMarc-André Lureau goto label; \ 88ea9ebc2cSMarc-André Lureau } \ 892562755eSEric Blake } while (0) 90ea9ebc2cSMarc-André Lureau 91ea9ebc2cSMarc-André Lureau #define CHECK_DEAD_GOTO(c, stream, rerror, label) \ 92ea9ebc2cSMarc-André Lureau do { \ 93ea9ebc2cSMarc-André Lureau if (!(c)->context || !PA_CONTEXT_IS_GOOD (pa_context_get_state((c)->context)) || \ 94ea9ebc2cSMarc-André Lureau !(stream) || !PA_STREAM_IS_GOOD (pa_stream_get_state ((stream)))) { \ 95ea9ebc2cSMarc-André Lureau if (((c)->context && pa_context_get_state ((c)->context) == PA_CONTEXT_FAILED) || \ 96ea9ebc2cSMarc-André Lureau ((stream) && pa_stream_get_state ((stream)) == PA_STREAM_FAILED)) { \ 97ea9ebc2cSMarc-André Lureau if (rerror) { \ 98ea9ebc2cSMarc-André Lureau *(rerror) = pa_context_errno ((c)->context); \ 99ea9ebc2cSMarc-André Lureau } \ 100ea9ebc2cSMarc-André Lureau } else { \ 101ea9ebc2cSMarc-André Lureau if (rerror) { \ 102ea9ebc2cSMarc-André Lureau *(rerror) = PA_ERR_BADSTATE; \ 103ea9ebc2cSMarc-André Lureau } \ 104ea9ebc2cSMarc-André Lureau } \ 105ea9ebc2cSMarc-André Lureau goto label; \ 106ea9ebc2cSMarc-André Lureau } \ 1072562755eSEric Blake } while (0) 108ea9ebc2cSMarc-André Lureau 109ea9ebc2cSMarc-André Lureau static int qpa_simple_read (PAVoiceIn *p, void *data, size_t length, int *rerror) 110ea9ebc2cSMarc-André Lureau { 1119a644c4bSKővágó, Zoltán paaudio *g = p->g; 112ea9ebc2cSMarc-André Lureau 113ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_lock (g->mainloop); 114ea9ebc2cSMarc-André Lureau 115ea9ebc2cSMarc-André Lureau CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); 116ea9ebc2cSMarc-André Lureau 117ea9ebc2cSMarc-André Lureau while (length > 0) { 118ea9ebc2cSMarc-André Lureau size_t l; 119ea9ebc2cSMarc-André Lureau 120ea9ebc2cSMarc-André Lureau while (!p->read_data) { 121ea9ebc2cSMarc-André Lureau int r; 122ea9ebc2cSMarc-André Lureau 123ea9ebc2cSMarc-André Lureau r = pa_stream_peek (p->stream, &p->read_data, &p->read_length); 124ea9ebc2cSMarc-André Lureau CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail); 125ea9ebc2cSMarc-André Lureau 126ea9ebc2cSMarc-André Lureau if (!p->read_data) { 127ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_wait (g->mainloop); 128ea9ebc2cSMarc-André Lureau CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); 129ea9ebc2cSMarc-André Lureau } else { 130ea9ebc2cSMarc-André Lureau p->read_index = 0; 131ea9ebc2cSMarc-André Lureau } 132ea9ebc2cSMarc-André Lureau } 133ea9ebc2cSMarc-André Lureau 134ea9ebc2cSMarc-André Lureau l = p->read_length < length ? p->read_length : length; 135ea9ebc2cSMarc-André Lureau memcpy (data, (const uint8_t *) p->read_data+p->read_index, l); 136ea9ebc2cSMarc-André Lureau 137ea9ebc2cSMarc-André Lureau data = (uint8_t *) data + l; 138ea9ebc2cSMarc-André Lureau length -= l; 139ea9ebc2cSMarc-André Lureau 140ea9ebc2cSMarc-André Lureau p->read_index += l; 141ea9ebc2cSMarc-André Lureau p->read_length -= l; 142ea9ebc2cSMarc-André Lureau 143ea9ebc2cSMarc-André Lureau if (!p->read_length) { 144ea9ebc2cSMarc-André Lureau int r; 145ea9ebc2cSMarc-André Lureau 146ea9ebc2cSMarc-André Lureau r = pa_stream_drop (p->stream); 147ea9ebc2cSMarc-André Lureau p->read_data = NULL; 148ea9ebc2cSMarc-André Lureau p->read_length = 0; 149ea9ebc2cSMarc-André Lureau p->read_index = 0; 150ea9ebc2cSMarc-André Lureau 151ea9ebc2cSMarc-André Lureau CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail); 152ea9ebc2cSMarc-André Lureau } 153ea9ebc2cSMarc-André Lureau } 154ea9ebc2cSMarc-André Lureau 155ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_unlock (g->mainloop); 156ea9ebc2cSMarc-André Lureau return 0; 157ea9ebc2cSMarc-André Lureau 158ea9ebc2cSMarc-André Lureau unlock_and_fail: 159ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_unlock (g->mainloop); 160ea9ebc2cSMarc-André Lureau return -1; 161ea9ebc2cSMarc-André Lureau } 162ea9ebc2cSMarc-André Lureau 163ea9ebc2cSMarc-André Lureau static int qpa_simple_write (PAVoiceOut *p, const void *data, size_t length, int *rerror) 164ea9ebc2cSMarc-André Lureau { 1659a644c4bSKővágó, Zoltán paaudio *g = p->g; 166ea9ebc2cSMarc-André Lureau 167ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_lock (g->mainloop); 168ea9ebc2cSMarc-André Lureau 169ea9ebc2cSMarc-André Lureau CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); 170ea9ebc2cSMarc-André Lureau 171ea9ebc2cSMarc-André Lureau while (length > 0) { 172ea9ebc2cSMarc-André Lureau size_t l; 173ea9ebc2cSMarc-André Lureau int r; 174ea9ebc2cSMarc-André Lureau 175ea9ebc2cSMarc-André Lureau while (!(l = pa_stream_writable_size (p->stream))) { 176ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_wait (g->mainloop); 177ea9ebc2cSMarc-André Lureau CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); 178ea9ebc2cSMarc-André Lureau } 179ea9ebc2cSMarc-André Lureau 180ea9ebc2cSMarc-André Lureau CHECK_SUCCESS_GOTO (g, rerror, l != (size_t) -1, unlock_and_fail); 181ea9ebc2cSMarc-André Lureau 182ea9ebc2cSMarc-André Lureau if (l > length) { 183ea9ebc2cSMarc-André Lureau l = length; 184ea9ebc2cSMarc-André Lureau } 185ea9ebc2cSMarc-André Lureau 186ea9ebc2cSMarc-André Lureau r = pa_stream_write (p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE); 187ea9ebc2cSMarc-André Lureau CHECK_SUCCESS_GOTO (g, rerror, r >= 0, unlock_and_fail); 188ea9ebc2cSMarc-André Lureau 189ea9ebc2cSMarc-André Lureau data = (const uint8_t *) data + l; 190ea9ebc2cSMarc-André Lureau length -= l; 191ea9ebc2cSMarc-André Lureau } 192ea9ebc2cSMarc-André Lureau 193ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_unlock (g->mainloop); 194ea9ebc2cSMarc-André Lureau return 0; 195ea9ebc2cSMarc-André Lureau 196ea9ebc2cSMarc-André Lureau unlock_and_fail: 197ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_unlock (g->mainloop); 198ea9ebc2cSMarc-André Lureau return -1; 199ea9ebc2cSMarc-André Lureau } 200ea9ebc2cSMarc-André Lureau 201b8e59f18Smalc static void *qpa_thread_out (void *arg) 202b8e59f18Smalc { 203b8e59f18Smalc PAVoiceOut *pa = arg; 204b8e59f18Smalc HWVoiceOut *hw = &pa->hw; 205b8e59f18Smalc 206470bcabdSAlistair Francis if (audio_pt_lock(&pa->pt, __func__)) { 207b8e59f18Smalc return NULL; 208b8e59f18Smalc } 209b8e59f18Smalc 210b8e59f18Smalc for (;;) { 211b8e59f18Smalc int decr, to_mix, rpos; 212b8e59f18Smalc 213b8e59f18Smalc for (;;) { 214b8e59f18Smalc if (pa->done) { 215b8e59f18Smalc goto exit; 216b8e59f18Smalc } 217b8e59f18Smalc 2186315633bSGerd Hoffmann if (pa->live > 0) { 219b8e59f18Smalc break; 220b8e59f18Smalc } 221b8e59f18Smalc 222470bcabdSAlistair Francis if (audio_pt_wait(&pa->pt, __func__)) { 223b8e59f18Smalc goto exit; 224b8e59f18Smalc } 225b8e59f18Smalc } 226b8e59f18Smalc 2272c324b28SKővágó, Zoltán decr = to_mix = audio_MIN(pa->live, pa->samples >> 5); 2286315633bSGerd Hoffmann rpos = pa->rpos; 229b8e59f18Smalc 230470bcabdSAlistair Francis if (audio_pt_unlock(&pa->pt, __func__)) { 231b8e59f18Smalc return NULL; 232b8e59f18Smalc } 233b8e59f18Smalc 234b8e59f18Smalc while (to_mix) { 235b8e59f18Smalc int error; 236b8e59f18Smalc int chunk = audio_MIN (to_mix, hw->samples - rpos); 2371ea879e5Smalc struct st_sample *src = hw->mix_buf + rpos; 238b8e59f18Smalc 239b8e59f18Smalc hw->clip (pa->pcm_buf, src, chunk); 240b8e59f18Smalc 241ea9ebc2cSMarc-André Lureau if (qpa_simple_write (pa, pa->pcm_buf, 242b8e59f18Smalc chunk << hw->info.shift, &error) < 0) { 243b8e59f18Smalc qpa_logerr (error, "pa_simple_write failed\n"); 244b8e59f18Smalc return NULL; 245b8e59f18Smalc } 246b8e59f18Smalc 247b8e59f18Smalc rpos = (rpos + chunk) % hw->samples; 248b8e59f18Smalc to_mix -= chunk; 249b8e59f18Smalc } 250b8e59f18Smalc 251470bcabdSAlistair Francis if (audio_pt_lock(&pa->pt, __func__)) { 252b8e59f18Smalc return NULL; 253b8e59f18Smalc } 254b8e59f18Smalc 255b8e59f18Smalc pa->rpos = rpos; 2566315633bSGerd Hoffmann pa->live -= decr; 257b8e59f18Smalc pa->decr += decr; 258b8e59f18Smalc } 259b8e59f18Smalc 260b8e59f18Smalc exit: 261470bcabdSAlistair Francis audio_pt_unlock(&pa->pt, __func__); 262b8e59f18Smalc return NULL; 263b8e59f18Smalc } 264b8e59f18Smalc 265bdff253cSmalc static int qpa_run_out (HWVoiceOut *hw, int live) 266b8e59f18Smalc { 267bdff253cSmalc int decr; 268b8e59f18Smalc PAVoiceOut *pa = (PAVoiceOut *) hw; 269b8e59f18Smalc 270470bcabdSAlistair Francis if (audio_pt_lock(&pa->pt, __func__)) { 271b8e59f18Smalc return 0; 272b8e59f18Smalc } 273b8e59f18Smalc 274b8e59f18Smalc decr = audio_MIN (live, pa->decr); 275b8e59f18Smalc pa->decr -= decr; 276b8e59f18Smalc pa->live = live - decr; 277b8e59f18Smalc hw->rpos = pa->rpos; 278b8e59f18Smalc if (pa->live > 0) { 279470bcabdSAlistair Francis audio_pt_unlock_and_signal(&pa->pt, __func__); 280b8e59f18Smalc } 281b8e59f18Smalc else { 282470bcabdSAlistair Francis audio_pt_unlock(&pa->pt, __func__); 283b8e59f18Smalc } 284b8e59f18Smalc return decr; 285b8e59f18Smalc } 286b8e59f18Smalc 287b8e59f18Smalc static int qpa_write (SWVoiceOut *sw, void *buf, int len) 288b8e59f18Smalc { 289b8e59f18Smalc return audio_pcm_sw_write (sw, buf, len); 290b8e59f18Smalc } 291b8e59f18Smalc 292b8e59f18Smalc /* capture */ 293b8e59f18Smalc static void *qpa_thread_in (void *arg) 294b8e59f18Smalc { 295b8e59f18Smalc PAVoiceIn *pa = arg; 296b8e59f18Smalc HWVoiceIn *hw = &pa->hw; 297b8e59f18Smalc 298470bcabdSAlistair Francis if (audio_pt_lock(&pa->pt, __func__)) { 299b8e59f18Smalc return NULL; 300b8e59f18Smalc } 301b8e59f18Smalc 302b8e59f18Smalc for (;;) { 303b8e59f18Smalc int incr, to_grab, wpos; 304b8e59f18Smalc 305b8e59f18Smalc for (;;) { 306b8e59f18Smalc if (pa->done) { 307b8e59f18Smalc goto exit; 308b8e59f18Smalc } 309b8e59f18Smalc 3106315633bSGerd Hoffmann if (pa->dead > 0) { 311b8e59f18Smalc break; 312b8e59f18Smalc } 313b8e59f18Smalc 314470bcabdSAlistair Francis if (audio_pt_wait(&pa->pt, __func__)) { 315b8e59f18Smalc goto exit; 316b8e59f18Smalc } 317b8e59f18Smalc } 318b8e59f18Smalc 3192c324b28SKővágó, Zoltán incr = to_grab = audio_MIN(pa->dead, pa->samples >> 5); 3206315633bSGerd Hoffmann wpos = pa->wpos; 321b8e59f18Smalc 322470bcabdSAlistair Francis if (audio_pt_unlock(&pa->pt, __func__)) { 323b8e59f18Smalc return NULL; 324b8e59f18Smalc } 325b8e59f18Smalc 326b8e59f18Smalc while (to_grab) { 327b8e59f18Smalc int error; 328b8e59f18Smalc int chunk = audio_MIN (to_grab, hw->samples - wpos); 329b8e59f18Smalc void *buf = advance (pa->pcm_buf, wpos); 330b8e59f18Smalc 331ea9ebc2cSMarc-André Lureau if (qpa_simple_read (pa, buf, 332b8e59f18Smalc chunk << hw->info.shift, &error) < 0) { 333b8e59f18Smalc qpa_logerr (error, "pa_simple_read failed\n"); 334b8e59f18Smalc return NULL; 335b8e59f18Smalc } 336b8e59f18Smalc 33700e07679SMichael Walle hw->conv (hw->conv_buf + wpos, buf, chunk); 338b8e59f18Smalc wpos = (wpos + chunk) % hw->samples; 339b8e59f18Smalc to_grab -= chunk; 340b8e59f18Smalc } 341b8e59f18Smalc 342470bcabdSAlistair Francis if (audio_pt_lock(&pa->pt, __func__)) { 343b8e59f18Smalc return NULL; 344b8e59f18Smalc } 345b8e59f18Smalc 346b8e59f18Smalc pa->wpos = wpos; 347b8e59f18Smalc pa->dead -= incr; 348b8e59f18Smalc pa->incr += incr; 349b8e59f18Smalc } 350b8e59f18Smalc 351b8e59f18Smalc exit: 352470bcabdSAlistair Francis audio_pt_unlock(&pa->pt, __func__); 353b8e59f18Smalc return NULL; 354b8e59f18Smalc } 355b8e59f18Smalc 356b8e59f18Smalc static int qpa_run_in (HWVoiceIn *hw) 357b8e59f18Smalc { 358b8e59f18Smalc int live, incr, dead; 359b8e59f18Smalc PAVoiceIn *pa = (PAVoiceIn *) hw; 360b8e59f18Smalc 361470bcabdSAlistair Francis if (audio_pt_lock(&pa->pt, __func__)) { 362b8e59f18Smalc return 0; 363b8e59f18Smalc } 364b8e59f18Smalc 365b8e59f18Smalc live = audio_pcm_hw_get_live_in (hw); 366b8e59f18Smalc dead = hw->samples - live; 367b8e59f18Smalc incr = audio_MIN (dead, pa->incr); 368b8e59f18Smalc pa->incr -= incr; 369b8e59f18Smalc pa->dead = dead - incr; 370b8e59f18Smalc hw->wpos = pa->wpos; 371b8e59f18Smalc if (pa->dead > 0) { 372470bcabdSAlistair Francis audio_pt_unlock_and_signal(&pa->pt, __func__); 373b8e59f18Smalc } 374b8e59f18Smalc else { 375470bcabdSAlistair Francis audio_pt_unlock(&pa->pt, __func__); 376b8e59f18Smalc } 377b8e59f18Smalc return incr; 378b8e59f18Smalc } 379b8e59f18Smalc 380b8e59f18Smalc static int qpa_read (SWVoiceIn *sw, void *buf, int len) 381b8e59f18Smalc { 382b8e59f18Smalc return audio_pcm_sw_read (sw, buf, len); 383b8e59f18Smalc } 384b8e59f18Smalc 38585bc5852SKővágó, Zoltán static pa_sample_format_t audfmt_to_pa (AudioFormat afmt, int endianness) 386b8e59f18Smalc { 387b8e59f18Smalc int format; 388b8e59f18Smalc 389b8e59f18Smalc switch (afmt) { 39085bc5852SKővágó, Zoltán case AUDIO_FORMAT_S8: 39185bc5852SKővágó, Zoltán case AUDIO_FORMAT_U8: 392b8e59f18Smalc format = PA_SAMPLE_U8; 393b8e59f18Smalc break; 39485bc5852SKővágó, Zoltán case AUDIO_FORMAT_S16: 39585bc5852SKővágó, Zoltán case AUDIO_FORMAT_U16: 396b8e59f18Smalc format = endianness ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE; 397b8e59f18Smalc break; 39885bc5852SKővágó, Zoltán case AUDIO_FORMAT_S32: 39985bc5852SKővágó, Zoltán case AUDIO_FORMAT_U32: 400b8e59f18Smalc format = endianness ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE; 401b8e59f18Smalc break; 402b8e59f18Smalc default: 403b8e59f18Smalc dolog ("Internal logic error: Bad audio format %d\n", afmt); 404b8e59f18Smalc format = PA_SAMPLE_U8; 405b8e59f18Smalc break; 406b8e59f18Smalc } 407b8e59f18Smalc return format; 408b8e59f18Smalc } 409b8e59f18Smalc 41085bc5852SKővágó, Zoltán static AudioFormat pa_to_audfmt (pa_sample_format_t fmt, int *endianness) 411b8e59f18Smalc { 412b8e59f18Smalc switch (fmt) { 413b8e59f18Smalc case PA_SAMPLE_U8: 41485bc5852SKővágó, Zoltán return AUDIO_FORMAT_U8; 415b8e59f18Smalc case PA_SAMPLE_S16BE: 416b8e59f18Smalc *endianness = 1; 41785bc5852SKővágó, Zoltán return AUDIO_FORMAT_S16; 418b8e59f18Smalc case PA_SAMPLE_S16LE: 419b8e59f18Smalc *endianness = 0; 42085bc5852SKővágó, Zoltán return AUDIO_FORMAT_S16; 421b8e59f18Smalc case PA_SAMPLE_S32BE: 422b8e59f18Smalc *endianness = 1; 42385bc5852SKővágó, Zoltán return AUDIO_FORMAT_S32; 424b8e59f18Smalc case PA_SAMPLE_S32LE: 425b8e59f18Smalc *endianness = 0; 42685bc5852SKővágó, Zoltán return AUDIO_FORMAT_S32; 427b8e59f18Smalc default: 428b8e59f18Smalc dolog ("Internal logic error: Bad pa_sample_format %d\n", fmt); 42985bc5852SKővágó, Zoltán return AUDIO_FORMAT_U8; 430b8e59f18Smalc } 431b8e59f18Smalc } 432b8e59f18Smalc 433ea9ebc2cSMarc-André Lureau static void context_state_cb (pa_context *c, void *userdata) 434ea9ebc2cSMarc-André Lureau { 4359a644c4bSKővágó, Zoltán paaudio *g = userdata; 436ea9ebc2cSMarc-André Lureau 437ea9ebc2cSMarc-André Lureau switch (pa_context_get_state(c)) { 438ea9ebc2cSMarc-André Lureau case PA_CONTEXT_READY: 439ea9ebc2cSMarc-André Lureau case PA_CONTEXT_TERMINATED: 440ea9ebc2cSMarc-André Lureau case PA_CONTEXT_FAILED: 441ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_signal (g->mainloop, 0); 442ea9ebc2cSMarc-André Lureau break; 443ea9ebc2cSMarc-André Lureau 444ea9ebc2cSMarc-André Lureau case PA_CONTEXT_UNCONNECTED: 445ea9ebc2cSMarc-André Lureau case PA_CONTEXT_CONNECTING: 446ea9ebc2cSMarc-André Lureau case PA_CONTEXT_AUTHORIZING: 447ea9ebc2cSMarc-André Lureau case PA_CONTEXT_SETTING_NAME: 448ea9ebc2cSMarc-André Lureau break; 449ea9ebc2cSMarc-André Lureau } 450ea9ebc2cSMarc-André Lureau } 451ea9ebc2cSMarc-André Lureau 452ea9ebc2cSMarc-André Lureau static void stream_state_cb (pa_stream *s, void * userdata) 453ea9ebc2cSMarc-André Lureau { 4549a644c4bSKővágó, Zoltán paaudio *g = userdata; 455ea9ebc2cSMarc-André Lureau 456ea9ebc2cSMarc-André Lureau switch (pa_stream_get_state (s)) { 457ea9ebc2cSMarc-André Lureau 458ea9ebc2cSMarc-André Lureau case PA_STREAM_READY: 459ea9ebc2cSMarc-André Lureau case PA_STREAM_FAILED: 460ea9ebc2cSMarc-André Lureau case PA_STREAM_TERMINATED: 461ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_signal (g->mainloop, 0); 462ea9ebc2cSMarc-André Lureau break; 463ea9ebc2cSMarc-André Lureau 464ea9ebc2cSMarc-André Lureau case PA_STREAM_UNCONNECTED: 465ea9ebc2cSMarc-André Lureau case PA_STREAM_CREATING: 466ea9ebc2cSMarc-André Lureau break; 467ea9ebc2cSMarc-André Lureau } 468ea9ebc2cSMarc-André Lureau } 469ea9ebc2cSMarc-André Lureau 470ea9ebc2cSMarc-André Lureau static void stream_request_cb (pa_stream *s, size_t length, void *userdata) 471ea9ebc2cSMarc-André Lureau { 4729a644c4bSKővágó, Zoltán paaudio *g = userdata; 473ea9ebc2cSMarc-André Lureau 474ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_signal (g->mainloop, 0); 475ea9ebc2cSMarc-André Lureau } 476ea9ebc2cSMarc-André Lureau 477ea9ebc2cSMarc-André Lureau static pa_stream *qpa_simple_new ( 4789a644c4bSKővágó, Zoltán paaudio *g, 479ea9ebc2cSMarc-André Lureau const char *name, 480ea9ebc2cSMarc-André Lureau pa_stream_direction_t dir, 481ea9ebc2cSMarc-André Lureau const char *dev, 482ea9ebc2cSMarc-André Lureau const pa_sample_spec *ss, 483ea9ebc2cSMarc-André Lureau const pa_channel_map *map, 484ea9ebc2cSMarc-André Lureau const pa_buffer_attr *attr, 485ea9ebc2cSMarc-André Lureau int *rerror) 486ea9ebc2cSMarc-André Lureau { 487ea9ebc2cSMarc-André Lureau int r; 488ea9ebc2cSMarc-André Lureau pa_stream *stream; 489ea9ebc2cSMarc-André Lureau 490ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_lock (g->mainloop); 491ea9ebc2cSMarc-André Lureau 492ea9ebc2cSMarc-André Lureau stream = pa_stream_new (g->context, name, ss, map); 493ea9ebc2cSMarc-André Lureau if (!stream) { 494ea9ebc2cSMarc-André Lureau goto fail; 495ea9ebc2cSMarc-André Lureau } 496ea9ebc2cSMarc-André Lureau 497ea9ebc2cSMarc-André Lureau pa_stream_set_state_callback (stream, stream_state_cb, g); 498ea9ebc2cSMarc-André Lureau pa_stream_set_read_callback (stream, stream_request_cb, g); 499ea9ebc2cSMarc-André Lureau pa_stream_set_write_callback (stream, stream_request_cb, g); 500ea9ebc2cSMarc-André Lureau 501ea9ebc2cSMarc-André Lureau if (dir == PA_STREAM_PLAYBACK) { 502ea9ebc2cSMarc-André Lureau r = pa_stream_connect_playback (stream, dev, attr, 503ea9ebc2cSMarc-André Lureau PA_STREAM_INTERPOLATE_TIMING 5048f473dd1SGerd Hoffmann #ifdef PA_STREAM_ADJUST_LATENCY 505ea9ebc2cSMarc-André Lureau |PA_STREAM_ADJUST_LATENCY 5068f473dd1SGerd Hoffmann #endif 507ea9ebc2cSMarc-André Lureau |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); 508ea9ebc2cSMarc-André Lureau } else { 509ea9ebc2cSMarc-André Lureau r = pa_stream_connect_record (stream, dev, attr, 510ea9ebc2cSMarc-André Lureau PA_STREAM_INTERPOLATE_TIMING 5118f473dd1SGerd Hoffmann #ifdef PA_STREAM_ADJUST_LATENCY 512ea9ebc2cSMarc-André Lureau |PA_STREAM_ADJUST_LATENCY 5138f473dd1SGerd Hoffmann #endif 514ea9ebc2cSMarc-André Lureau |PA_STREAM_AUTO_TIMING_UPDATE); 515ea9ebc2cSMarc-André Lureau } 516ea9ebc2cSMarc-André Lureau 517ea9ebc2cSMarc-André Lureau if (r < 0) { 518ea9ebc2cSMarc-André Lureau goto fail; 519ea9ebc2cSMarc-André Lureau } 520ea9ebc2cSMarc-André Lureau 521ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_unlock (g->mainloop); 522ea9ebc2cSMarc-André Lureau 523ea9ebc2cSMarc-André Lureau return stream; 524ea9ebc2cSMarc-André Lureau 525ea9ebc2cSMarc-André Lureau fail: 526ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_unlock (g->mainloop); 527ea9ebc2cSMarc-André Lureau 528ea9ebc2cSMarc-André Lureau if (stream) { 529ea9ebc2cSMarc-André Lureau pa_stream_unref (stream); 530ea9ebc2cSMarc-André Lureau } 531ea9ebc2cSMarc-André Lureau 532d6c05bbfSGerd Hoffmann *rerror = pa_context_errno (g->context); 533ea9ebc2cSMarc-André Lureau 534ea9ebc2cSMarc-André Lureau return NULL; 535ea9ebc2cSMarc-André Lureau } 536ea9ebc2cSMarc-André Lureau 5375706db1dSKővágó, Zoltán static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, 5385706db1dSKővágó, Zoltán void *drv_opaque) 539b8e59f18Smalc { 540b8e59f18Smalc int error; 5419a644c4bSKővágó, Zoltán pa_sample_spec ss; 5429a644c4bSKővágó, Zoltán pa_buffer_attr ba; 5431ea879e5Smalc struct audsettings obt_as = *as; 544b8e59f18Smalc PAVoiceOut *pa = (PAVoiceOut *) hw; 5459a644c4bSKővágó, Zoltán paaudio *g = pa->g = drv_opaque; 5462c324b28SKővágó, Zoltán AudiodevPaOptions *popts = &g->dev->u.pa; 5472c324b28SKővágó, Zoltán AudiodevPaPerDirectionOptions *ppdo = popts->out; 548b8e59f18Smalc 549b8e59f18Smalc ss.format = audfmt_to_pa (as->fmt, as->endianness); 550b8e59f18Smalc ss.channels = as->nchannels; 551b8e59f18Smalc ss.rate = as->freq; 552b8e59f18Smalc 553f6142777SMartin Schrodt ba.tlength = pa_usec_to_bytes(ppdo->latency, &ss); 554f6142777SMartin Schrodt ba.minreq = -1; 555e6d16fa4SGerd Hoffmann ba.maxlength = -1; 556e6d16fa4SGerd Hoffmann ba.prebuf = -1; 557e6d16fa4SGerd Hoffmann 558b8e59f18Smalc obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); 559b8e59f18Smalc 560ea9ebc2cSMarc-André Lureau pa->stream = qpa_simple_new ( 5619a644c4bSKővágó, Zoltán g, 562b8e59f18Smalc "qemu", 563b8e59f18Smalc PA_STREAM_PLAYBACK, 5642c324b28SKővágó, Zoltán ppdo->has_name ? ppdo->name : NULL, 565b8e59f18Smalc &ss, 566b8e59f18Smalc NULL, /* channel map */ 567e6d16fa4SGerd Hoffmann &ba, /* buffering attributes */ 568b8e59f18Smalc &error 569b8e59f18Smalc ); 570ea9ebc2cSMarc-André Lureau if (!pa->stream) { 571b8e59f18Smalc qpa_logerr (error, "pa_simple_new for playback failed\n"); 572b8e59f18Smalc goto fail1; 573b8e59f18Smalc } 574b8e59f18Smalc 575b8e59f18Smalc audio_pcm_init_info (&hw->info, &obt_as); 5762c324b28SKővágó, Zoltán hw->samples = pa->samples = audio_buffer_samples( 577baea032eSMartin Schrodt qapi_AudiodevPaPerDirectionOptions_base(ppdo), 578baea032eSMartin Schrodt &obt_as, ppdo->buffer_length); 579470bcabdSAlistair Francis pa->pcm_buf = audio_calloc(__func__, hw->samples, 1 << hw->info.shift); 5806315633bSGerd Hoffmann pa->rpos = hw->rpos; 581b8e59f18Smalc if (!pa->pcm_buf) { 582b8e59f18Smalc dolog ("Could not allocate buffer (%d bytes)\n", 583b8e59f18Smalc hw->samples << hw->info.shift); 584b8e59f18Smalc goto fail2; 585b8e59f18Smalc } 586b8e59f18Smalc 587470bcabdSAlistair Francis if (audio_pt_init(&pa->pt, qpa_thread_out, hw, AUDIO_CAP, __func__)) { 588b8e59f18Smalc goto fail3; 589b8e59f18Smalc } 590b8e59f18Smalc 591b8e59f18Smalc return 0; 592b8e59f18Smalc 593b8e59f18Smalc fail3: 5947267c094SAnthony Liguori g_free (pa->pcm_buf); 595b8e59f18Smalc pa->pcm_buf = NULL; 596b8e59f18Smalc fail2: 597ea9ebc2cSMarc-André Lureau if (pa->stream) { 598ea9ebc2cSMarc-André Lureau pa_stream_unref (pa->stream); 599ea9ebc2cSMarc-André Lureau pa->stream = NULL; 600ea9ebc2cSMarc-André Lureau } 601b8e59f18Smalc fail1: 602b8e59f18Smalc return -1; 603b8e59f18Smalc } 604b8e59f18Smalc 6055706db1dSKővágó, Zoltán static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) 606b8e59f18Smalc { 607b8e59f18Smalc int error; 6089a644c4bSKővágó, Zoltán pa_sample_spec ss; 609ade10301SMartin Schrodt pa_buffer_attr ba; 6101ea879e5Smalc struct audsettings obt_as = *as; 611b8e59f18Smalc PAVoiceIn *pa = (PAVoiceIn *) hw; 6129a644c4bSKővágó, Zoltán paaudio *g = pa->g = drv_opaque; 6132c324b28SKővágó, Zoltán AudiodevPaOptions *popts = &g->dev->u.pa; 6142c324b28SKővágó, Zoltán AudiodevPaPerDirectionOptions *ppdo = popts->in; 615b8e59f18Smalc 616b8e59f18Smalc ss.format = audfmt_to_pa (as->fmt, as->endianness); 617b8e59f18Smalc ss.channels = as->nchannels; 618b8e59f18Smalc ss.rate = as->freq; 619b8e59f18Smalc 620ade10301SMartin Schrodt ba.fragsize = pa_usec_to_bytes(ppdo->latency, &ss); 621ade10301SMartin Schrodt ba.maxlength = -1; 622ade10301SMartin Schrodt ba.minreq = -1; 623ade10301SMartin Schrodt ba.prebuf = -1; 624ade10301SMartin Schrodt 625b8e59f18Smalc obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); 626b8e59f18Smalc 627ea9ebc2cSMarc-André Lureau pa->stream = qpa_simple_new ( 6289a644c4bSKővágó, Zoltán g, 629b8e59f18Smalc "qemu", 630b8e59f18Smalc PA_STREAM_RECORD, 6312c324b28SKővágó, Zoltán ppdo->has_name ? ppdo->name : NULL, 632b8e59f18Smalc &ss, 633b8e59f18Smalc NULL, /* channel map */ 634ade10301SMartin Schrodt &ba, /* buffering attributes */ 635b8e59f18Smalc &error 636b8e59f18Smalc ); 637ea9ebc2cSMarc-André Lureau if (!pa->stream) { 638b8e59f18Smalc qpa_logerr (error, "pa_simple_new for capture failed\n"); 639b8e59f18Smalc goto fail1; 640b8e59f18Smalc } 641b8e59f18Smalc 642b8e59f18Smalc audio_pcm_init_info (&hw->info, &obt_as); 6432c324b28SKővágó, Zoltán hw->samples = pa->samples = audio_buffer_samples( 644baea032eSMartin Schrodt qapi_AudiodevPaPerDirectionOptions_base(ppdo), 645baea032eSMartin Schrodt &obt_as, ppdo->buffer_length); 646470bcabdSAlistair Francis pa->pcm_buf = audio_calloc(__func__, hw->samples, 1 << hw->info.shift); 6476315633bSGerd Hoffmann pa->wpos = hw->wpos; 648b8e59f18Smalc if (!pa->pcm_buf) { 649b8e59f18Smalc dolog ("Could not allocate buffer (%d bytes)\n", 650b8e59f18Smalc hw->samples << hw->info.shift); 651b8e59f18Smalc goto fail2; 652b8e59f18Smalc } 653b8e59f18Smalc 654470bcabdSAlistair Francis if (audio_pt_init(&pa->pt, qpa_thread_in, hw, AUDIO_CAP, __func__)) { 655b8e59f18Smalc goto fail3; 656b8e59f18Smalc } 657b8e59f18Smalc 658b8e59f18Smalc return 0; 659b8e59f18Smalc 660b8e59f18Smalc fail3: 6617267c094SAnthony Liguori g_free (pa->pcm_buf); 662b8e59f18Smalc pa->pcm_buf = NULL; 663b8e59f18Smalc fail2: 664ea9ebc2cSMarc-André Lureau if (pa->stream) { 665ea9ebc2cSMarc-André Lureau pa_stream_unref (pa->stream); 666ea9ebc2cSMarc-André Lureau pa->stream = NULL; 667ea9ebc2cSMarc-André Lureau } 668b8e59f18Smalc fail1: 669b8e59f18Smalc return -1; 670b8e59f18Smalc } 671b8e59f18Smalc 672b8e59f18Smalc static void qpa_fini_out (HWVoiceOut *hw) 673b8e59f18Smalc { 674b8e59f18Smalc void *ret; 675b8e59f18Smalc PAVoiceOut *pa = (PAVoiceOut *) hw; 676b8e59f18Smalc 677470bcabdSAlistair Francis audio_pt_lock(&pa->pt, __func__); 678b8e59f18Smalc pa->done = 1; 679470bcabdSAlistair Francis audio_pt_unlock_and_signal(&pa->pt, __func__); 680470bcabdSAlistair Francis audio_pt_join(&pa->pt, &ret, __func__); 681b8e59f18Smalc 682ea9ebc2cSMarc-André Lureau if (pa->stream) { 683ea9ebc2cSMarc-André Lureau pa_stream_unref (pa->stream); 684ea9ebc2cSMarc-André Lureau pa->stream = NULL; 685b8e59f18Smalc } 686b8e59f18Smalc 687470bcabdSAlistair Francis audio_pt_fini(&pa->pt, __func__); 6887267c094SAnthony Liguori g_free (pa->pcm_buf); 689b8e59f18Smalc pa->pcm_buf = NULL; 690b8e59f18Smalc } 691b8e59f18Smalc 692b8e59f18Smalc static void qpa_fini_in (HWVoiceIn *hw) 693b8e59f18Smalc { 694b8e59f18Smalc void *ret; 695b8e59f18Smalc PAVoiceIn *pa = (PAVoiceIn *) hw; 696b8e59f18Smalc 697470bcabdSAlistair Francis audio_pt_lock(&pa->pt, __func__); 698b8e59f18Smalc pa->done = 1; 699470bcabdSAlistair Francis audio_pt_unlock_and_signal(&pa->pt, __func__); 700470bcabdSAlistair Francis audio_pt_join(&pa->pt, &ret, __func__); 701b8e59f18Smalc 702ea9ebc2cSMarc-André Lureau if (pa->stream) { 703ea9ebc2cSMarc-André Lureau pa_stream_unref (pa->stream); 704ea9ebc2cSMarc-André Lureau pa->stream = NULL; 705b8e59f18Smalc } 706b8e59f18Smalc 707470bcabdSAlistair Francis audio_pt_fini(&pa->pt, __func__); 7087267c094SAnthony Liguori g_free (pa->pcm_buf); 709b8e59f18Smalc pa->pcm_buf = NULL; 710b8e59f18Smalc } 711b8e59f18Smalc 712b8e59f18Smalc static int qpa_ctl_out (HWVoiceOut *hw, int cmd, ...) 713b8e59f18Smalc { 7146e7a7f3dSMarc-André Lureau PAVoiceOut *pa = (PAVoiceOut *) hw; 7156e7a7f3dSMarc-André Lureau pa_operation *op; 7166e7a7f3dSMarc-André Lureau pa_cvolume v; 7179a644c4bSKővágó, Zoltán paaudio *g = pa->g; 7186e7a7f3dSMarc-André Lureau 7198f473dd1SGerd Hoffmann #ifdef PA_CHECK_VERSION /* macro is present in 0.9.16+ */ 7208f473dd1SGerd Hoffmann pa_cvolume_init (&v); /* function is present in 0.9.13+ */ 7218f473dd1SGerd Hoffmann #endif 7226e7a7f3dSMarc-André Lureau 7236e7a7f3dSMarc-André Lureau switch (cmd) { 7246e7a7f3dSMarc-André Lureau case VOICE_VOLUME: 7256e7a7f3dSMarc-André Lureau { 7266e7a7f3dSMarc-André Lureau SWVoiceOut *sw; 7276e7a7f3dSMarc-André Lureau va_list ap; 7286e7a7f3dSMarc-André Lureau 7296e7a7f3dSMarc-André Lureau va_start (ap, cmd); 7306e7a7f3dSMarc-André Lureau sw = va_arg (ap, SWVoiceOut *); 7316e7a7f3dSMarc-André Lureau va_end (ap); 7326e7a7f3dSMarc-André Lureau 7336e7a7f3dSMarc-André Lureau v.channels = 2; 7346e7a7f3dSMarc-André Lureau v.values[0] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.l) / UINT32_MAX; 7356e7a7f3dSMarc-André Lureau v.values[1] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.r) / UINT32_MAX; 7366e7a7f3dSMarc-André Lureau 7376e7a7f3dSMarc-André Lureau pa_threaded_mainloop_lock (g->mainloop); 7386e7a7f3dSMarc-André Lureau 7396e7a7f3dSMarc-André Lureau op = pa_context_set_sink_input_volume (g->context, 7406e7a7f3dSMarc-André Lureau pa_stream_get_index (pa->stream), 7416e7a7f3dSMarc-André Lureau &v, NULL, NULL); 7426e7a7f3dSMarc-André Lureau if (!op) 7436e7a7f3dSMarc-André Lureau qpa_logerr (pa_context_errno (g->context), 7446e7a7f3dSMarc-André Lureau "set_sink_input_volume() failed\n"); 7456e7a7f3dSMarc-André Lureau else 7466e7a7f3dSMarc-André Lureau pa_operation_unref (op); 7476e7a7f3dSMarc-André Lureau 7486e7a7f3dSMarc-André Lureau op = pa_context_set_sink_input_mute (g->context, 7496e7a7f3dSMarc-André Lureau pa_stream_get_index (pa->stream), 7506e7a7f3dSMarc-André Lureau sw->vol.mute, NULL, NULL); 7516e7a7f3dSMarc-André Lureau if (!op) { 7526e7a7f3dSMarc-André Lureau qpa_logerr (pa_context_errno (g->context), 7536e7a7f3dSMarc-André Lureau "set_sink_input_mute() failed\n"); 7546e7a7f3dSMarc-André Lureau } else { 7556e7a7f3dSMarc-André Lureau pa_operation_unref (op); 7566e7a7f3dSMarc-André Lureau } 7576e7a7f3dSMarc-André Lureau 7586e7a7f3dSMarc-André Lureau pa_threaded_mainloop_unlock (g->mainloop); 7596e7a7f3dSMarc-André Lureau } 7606e7a7f3dSMarc-André Lureau } 761b8e59f18Smalc return 0; 762b8e59f18Smalc } 763b8e59f18Smalc 764b8e59f18Smalc static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) 765b8e59f18Smalc { 7666e7a7f3dSMarc-André Lureau PAVoiceIn *pa = (PAVoiceIn *) hw; 7676e7a7f3dSMarc-André Lureau pa_operation *op; 7686e7a7f3dSMarc-André Lureau pa_cvolume v; 7699a644c4bSKővágó, Zoltán paaudio *g = pa->g; 7706e7a7f3dSMarc-André Lureau 7718f473dd1SGerd Hoffmann #ifdef PA_CHECK_VERSION 7726e7a7f3dSMarc-André Lureau pa_cvolume_init (&v); 7738f473dd1SGerd Hoffmann #endif 7746e7a7f3dSMarc-André Lureau 7756e7a7f3dSMarc-André Lureau switch (cmd) { 7766e7a7f3dSMarc-André Lureau case VOICE_VOLUME: 7776e7a7f3dSMarc-André Lureau { 7786e7a7f3dSMarc-André Lureau SWVoiceIn *sw; 7796e7a7f3dSMarc-André Lureau va_list ap; 7806e7a7f3dSMarc-André Lureau 7816e7a7f3dSMarc-André Lureau va_start (ap, cmd); 7826e7a7f3dSMarc-André Lureau sw = va_arg (ap, SWVoiceIn *); 7836e7a7f3dSMarc-André Lureau va_end (ap); 7846e7a7f3dSMarc-André Lureau 7856e7a7f3dSMarc-André Lureau v.channels = 2; 7866e7a7f3dSMarc-André Lureau v.values[0] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.l) / UINT32_MAX; 7876e7a7f3dSMarc-André Lureau v.values[1] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.r) / UINT32_MAX; 7886e7a7f3dSMarc-André Lureau 7896e7a7f3dSMarc-André Lureau pa_threaded_mainloop_lock (g->mainloop); 7906e7a7f3dSMarc-André Lureau 791e58ff62dSPeter Krempa op = pa_context_set_source_output_volume (g->context, 792e58ff62dSPeter Krempa pa_stream_get_index (pa->stream), 7936e7a7f3dSMarc-André Lureau &v, NULL, NULL); 7946e7a7f3dSMarc-André Lureau if (!op) { 7956e7a7f3dSMarc-André Lureau qpa_logerr (pa_context_errno (g->context), 796e58ff62dSPeter Krempa "set_source_output_volume() failed\n"); 7976e7a7f3dSMarc-André Lureau } else { 7986e7a7f3dSMarc-André Lureau pa_operation_unref(op); 7996e7a7f3dSMarc-André Lureau } 8006e7a7f3dSMarc-André Lureau 801e58ff62dSPeter Krempa op = pa_context_set_source_output_mute (g->context, 8026e7a7f3dSMarc-André Lureau pa_stream_get_index (pa->stream), 8036e7a7f3dSMarc-André Lureau sw->vol.mute, NULL, NULL); 8046e7a7f3dSMarc-André Lureau if (!op) { 8056e7a7f3dSMarc-André Lureau qpa_logerr (pa_context_errno (g->context), 806e58ff62dSPeter Krempa "set_source_output_mute() failed\n"); 8076e7a7f3dSMarc-André Lureau } else { 8086e7a7f3dSMarc-André Lureau pa_operation_unref (op); 8096e7a7f3dSMarc-André Lureau } 8106e7a7f3dSMarc-André Lureau 8116e7a7f3dSMarc-André Lureau pa_threaded_mainloop_unlock (g->mainloop); 8126e7a7f3dSMarc-André Lureau } 8136e7a7f3dSMarc-André Lureau } 814b8e59f18Smalc return 0; 815b8e59f18Smalc } 816b8e59f18Smalc 817baea032eSMartin Schrodt static int qpa_validate_per_direction_opts(Audiodev *dev, 818baea032eSMartin Schrodt AudiodevPaPerDirectionOptions *pdo) 819baea032eSMartin Schrodt { 820baea032eSMartin Schrodt if (!pdo->has_buffer_length) { 821baea032eSMartin Schrodt pdo->has_buffer_length = true; 822baea032eSMartin Schrodt pdo->buffer_length = 46440; 823baea032eSMartin Schrodt } 824f6142777SMartin Schrodt if (!pdo->has_latency) { 825f6142777SMartin Schrodt pdo->has_latency = true; 826f6142777SMartin Schrodt pdo->latency = 15000; 827f6142777SMartin Schrodt } 828baea032eSMartin Schrodt return 1; 829baea032eSMartin Schrodt } 830baea032eSMartin Schrodt 83171830221SKővágó, Zoltán static void *qpa_audio_init(Audiodev *dev) 832b8e59f18Smalc { 8332c324b28SKővágó, Zoltán paaudio *g; 8342c324b28SKővágó, Zoltán AudiodevPaOptions *popts = &dev->u.pa; 8352c324b28SKővágó, Zoltán const char *server; 8362c324b28SKővágó, Zoltán 8372c324b28SKővágó, Zoltán if (!popts->has_server) { 838d175505bSGerd Hoffmann char pidfile[64]; 839d175505bSGerd Hoffmann char *runtime; 840d175505bSGerd Hoffmann struct stat st; 841d175505bSGerd Hoffmann 842d175505bSGerd Hoffmann runtime = getenv("XDG_RUNTIME_DIR"); 843d175505bSGerd Hoffmann if (!runtime) { 844d175505bSGerd Hoffmann return NULL; 845d175505bSGerd Hoffmann } 846d175505bSGerd Hoffmann snprintf(pidfile, sizeof(pidfile), "%s/pulse/pid", runtime); 847d175505bSGerd Hoffmann if (stat(pidfile, &st) != 0) { 848d175505bSGerd Hoffmann return NULL; 849d175505bSGerd Hoffmann } 850d175505bSGerd Hoffmann } 851d175505bSGerd Hoffmann 8522c324b28SKővágó, Zoltán assert(dev->driver == AUDIODEV_DRIVER_PA); 8532c324b28SKővágó, Zoltán 8542c324b28SKővágó, Zoltán g = g_malloc(sizeof(paaudio)); 8552c324b28SKővágó, Zoltán server = popts->has_server ? popts->server : NULL; 8562c324b28SKővágó, Zoltán 857baea032eSMartin Schrodt if (!qpa_validate_per_direction_opts(dev, popts->in)) { 858baea032eSMartin Schrodt goto fail; 859baea032eSMartin Schrodt } 860baea032eSMartin Schrodt if (!qpa_validate_per_direction_opts(dev, popts->out)) { 861baea032eSMartin Schrodt goto fail; 862baea032eSMartin Schrodt } 863baea032eSMartin Schrodt 8642c324b28SKővágó, Zoltán g->dev = dev; 86549dd6d0dSKővágó, Zoltán g->mainloop = NULL; 86649dd6d0dSKővágó, Zoltán g->context = NULL; 867ea9ebc2cSMarc-André Lureau 868ea9ebc2cSMarc-André Lureau g->mainloop = pa_threaded_mainloop_new (); 869ea9ebc2cSMarc-André Lureau if (!g->mainloop) { 870ea9ebc2cSMarc-André Lureau goto fail; 871ea9ebc2cSMarc-André Lureau } 872ea9ebc2cSMarc-André Lureau 8739a644c4bSKővágó, Zoltán g->context = pa_context_new (pa_threaded_mainloop_get_api (g->mainloop), 8742c324b28SKővágó, Zoltán server); 875ea9ebc2cSMarc-André Lureau if (!g->context) { 876ea9ebc2cSMarc-André Lureau goto fail; 877ea9ebc2cSMarc-André Lureau } 878ea9ebc2cSMarc-André Lureau 879ea9ebc2cSMarc-André Lureau pa_context_set_state_callback (g->context, context_state_cb, g); 880ea9ebc2cSMarc-André Lureau 8812c324b28SKővágó, Zoltán if (pa_context_connect(g->context, server, 0, NULL) < 0) { 882ea9ebc2cSMarc-André Lureau qpa_logerr (pa_context_errno (g->context), 883ea9ebc2cSMarc-André Lureau "pa_context_connect() failed\n"); 884ea9ebc2cSMarc-André Lureau goto fail; 885ea9ebc2cSMarc-André Lureau } 886ea9ebc2cSMarc-André Lureau 887ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_lock (g->mainloop); 888ea9ebc2cSMarc-André Lureau 889ea9ebc2cSMarc-André Lureau if (pa_threaded_mainloop_start (g->mainloop) < 0) { 890ea9ebc2cSMarc-André Lureau goto unlock_and_fail; 891ea9ebc2cSMarc-André Lureau } 892ea9ebc2cSMarc-André Lureau 893ea9ebc2cSMarc-André Lureau for (;;) { 894ea9ebc2cSMarc-André Lureau pa_context_state_t state; 895ea9ebc2cSMarc-André Lureau 896ea9ebc2cSMarc-André Lureau state = pa_context_get_state (g->context); 897ea9ebc2cSMarc-André Lureau 898ea9ebc2cSMarc-André Lureau if (state == PA_CONTEXT_READY) { 899ea9ebc2cSMarc-André Lureau break; 900ea9ebc2cSMarc-André Lureau } 901ea9ebc2cSMarc-André Lureau 902ea9ebc2cSMarc-André Lureau if (!PA_CONTEXT_IS_GOOD (state)) { 903ea9ebc2cSMarc-André Lureau qpa_logerr (pa_context_errno (g->context), 904ea9ebc2cSMarc-André Lureau "Wrong context state\n"); 905ea9ebc2cSMarc-André Lureau goto unlock_and_fail; 906ea9ebc2cSMarc-André Lureau } 907ea9ebc2cSMarc-André Lureau 908ea9ebc2cSMarc-André Lureau /* Wait until the context is ready */ 909ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_wait (g->mainloop); 910ea9ebc2cSMarc-André Lureau } 911ea9ebc2cSMarc-André Lureau 912ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_unlock (g->mainloop); 913ea9ebc2cSMarc-André Lureau 9149a644c4bSKővágó, Zoltán return g; 915ea9ebc2cSMarc-André Lureau 916ea9ebc2cSMarc-André Lureau unlock_and_fail: 917ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_unlock (g->mainloop); 918ea9ebc2cSMarc-André Lureau fail: 919ea9ebc2cSMarc-André Lureau AUD_log (AUDIO_CAP, "Failed to initialize PA context"); 92049dd6d0dSKővágó, Zoltán qpa_audio_fini(g); 921ea9ebc2cSMarc-André Lureau return NULL; 922b8e59f18Smalc } 923b8e59f18Smalc 924b8e59f18Smalc static void qpa_audio_fini (void *opaque) 925b8e59f18Smalc { 926ea9ebc2cSMarc-André Lureau paaudio *g = opaque; 927ea9ebc2cSMarc-André Lureau 928ea9ebc2cSMarc-André Lureau if (g->mainloop) { 929ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_stop (g->mainloop); 930ea9ebc2cSMarc-André Lureau } 931ea9ebc2cSMarc-André Lureau 932ea9ebc2cSMarc-André Lureau if (g->context) { 933ea9ebc2cSMarc-André Lureau pa_context_disconnect (g->context); 934ea9ebc2cSMarc-André Lureau pa_context_unref (g->context); 935ea9ebc2cSMarc-André Lureau } 936ea9ebc2cSMarc-André Lureau 937ea9ebc2cSMarc-André Lureau if (g->mainloop) { 938ea9ebc2cSMarc-André Lureau pa_threaded_mainloop_free (g->mainloop); 939ea9ebc2cSMarc-André Lureau } 940ea9ebc2cSMarc-André Lureau 9419a644c4bSKővágó, Zoltán g_free(g); 942b8e59f18Smalc } 943b8e59f18Smalc 94435f4b58cSblueswir1 static struct audio_pcm_ops qpa_pcm_ops = { 9451dd3e4d1SJuan Quintela .init_out = qpa_init_out, 9461dd3e4d1SJuan Quintela .fini_out = qpa_fini_out, 9471dd3e4d1SJuan Quintela .run_out = qpa_run_out, 9481dd3e4d1SJuan Quintela .write = qpa_write, 9491dd3e4d1SJuan Quintela .ctl_out = qpa_ctl_out, 9501dd3e4d1SJuan Quintela 9511dd3e4d1SJuan Quintela .init_in = qpa_init_in, 9521dd3e4d1SJuan Quintela .fini_in = qpa_fini_in, 9531dd3e4d1SJuan Quintela .run_in = qpa_run_in, 9541dd3e4d1SJuan Quintela .read = qpa_read, 9551dd3e4d1SJuan Quintela .ctl_in = qpa_ctl_in 956b8e59f18Smalc }; 957b8e59f18Smalc 958d3893a39SGerd Hoffmann static struct audio_driver pa_audio_driver = { 959bee37f32SJuan Quintela .name = "pa", 960bee37f32SJuan Quintela .descr = "http://www.pulseaudio.org/", 961bee37f32SJuan Quintela .init = qpa_audio_init, 962bee37f32SJuan Quintela .fini = qpa_audio_fini, 963bee37f32SJuan Quintela .pcm_ops = &qpa_pcm_ops, 9641a4ea1e3SMichael S. Tsirkin .can_be_default = 1, 965bee37f32SJuan Quintela .max_voices_out = INT_MAX, 966bee37f32SJuan Quintela .max_voices_in = INT_MAX, 967bee37f32SJuan Quintela .voice_size_out = sizeof (PAVoiceOut), 9686e7a7f3dSMarc-André Lureau .voice_size_in = sizeof (PAVoiceIn), 9696e7a7f3dSMarc-André Lureau .ctl_caps = VOICE_VOLUME_CAP 970b8e59f18Smalc }; 971d3893a39SGerd Hoffmann 972d3893a39SGerd Hoffmann static void register_audio_pa(void) 973d3893a39SGerd Hoffmann { 974d3893a39SGerd Hoffmann audio_driver_register(&pa_audio_driver); 975d3893a39SGerd Hoffmann } 976d3893a39SGerd Hoffmann type_init(register_audio_pa); 977