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 139d34e6d8SKővágó, Zoltán typedef struct PAConnection { 149d34e6d8SKővágó, Zoltán char *server; 159d34e6d8SKővágó, Zoltán int refcount; 169d34e6d8SKővágó, Zoltán QTAILQ_ENTRY(PAConnection) list; 179d34e6d8SKővágó, Zoltán 189a644c4bSKővágó, Zoltán pa_threaded_mainloop *mainloop; 199a644c4bSKővágó, Zoltán pa_context *context; 209d34e6d8SKővágó, Zoltán } PAConnection; 219d34e6d8SKővágó, Zoltán 229d34e6d8SKővágó, Zoltán static QTAILQ_HEAD(PAConnectionHead, PAConnection) pa_conns = 239d34e6d8SKővágó, Zoltán QTAILQ_HEAD_INITIALIZER(pa_conns); 249d34e6d8SKővágó, Zoltán 259d34e6d8SKővágó, Zoltán typedef struct { 269d34e6d8SKővágó, Zoltán Audiodev *dev; 279d34e6d8SKővágó, Zoltán PAConnection *conn; 289a644c4bSKővágó, Zoltán } paaudio; 299a644c4bSKővágó, Zoltán 309a644c4bSKővágó, Zoltán typedef struct { 31b8e59f18Smalc HWVoiceOut hw; 32ea9ebc2cSMarc-André Lureau pa_stream *stream; 339a644c4bSKővágó, Zoltán paaudio *g; 34b8e59f18Smalc } PAVoiceOut; 35b8e59f18Smalc 36b8e59f18Smalc typedef struct { 37b8e59f18Smalc HWVoiceIn hw; 38ea9ebc2cSMarc-André Lureau pa_stream *stream; 39ea9ebc2cSMarc-André Lureau const void *read_data; 4049ddd7e1SKővágó, Zoltán size_t read_length; 419a644c4bSKővágó, Zoltán paaudio *g; 42b8e59f18Smalc } PAVoiceIn; 43b8e59f18Smalc 449d34e6d8SKővágó, Zoltán static void qpa_conn_fini(PAConnection *c); 4549dd6d0dSKővágó, Zoltán 46b8e59f18Smalc static void GCC_FMT_ATTR (2, 3) qpa_logerr (int err, const char *fmt, ...) 47b8e59f18Smalc { 48b8e59f18Smalc va_list ap; 49b8e59f18Smalc 50b8e59f18Smalc va_start (ap, fmt); 51b8e59f18Smalc AUD_vlog (AUDIO_CAP, fmt, ap); 52b8e59f18Smalc va_end (ap); 53b8e59f18Smalc 54b8e59f18Smalc AUD_log (AUDIO_CAP, "Reason: %s\n", pa_strerror (err)); 55b8e59f18Smalc } 56b8e59f18Smalc 578f473dd1SGerd Hoffmann #ifndef PA_CONTEXT_IS_GOOD 588f473dd1SGerd Hoffmann static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) 598f473dd1SGerd Hoffmann { 608f473dd1SGerd Hoffmann return 618f473dd1SGerd Hoffmann x == PA_CONTEXT_CONNECTING || 628f473dd1SGerd Hoffmann x == PA_CONTEXT_AUTHORIZING || 638f473dd1SGerd Hoffmann x == PA_CONTEXT_SETTING_NAME || 648f473dd1SGerd Hoffmann x == PA_CONTEXT_READY; 658f473dd1SGerd Hoffmann } 668f473dd1SGerd Hoffmann #endif 678f473dd1SGerd Hoffmann 688f473dd1SGerd Hoffmann #ifndef PA_STREAM_IS_GOOD 698f473dd1SGerd Hoffmann static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) 708f473dd1SGerd Hoffmann { 718f473dd1SGerd Hoffmann return 728f473dd1SGerd Hoffmann x == PA_STREAM_CREATING || 738f473dd1SGerd Hoffmann x == PA_STREAM_READY; 748f473dd1SGerd Hoffmann } 758f473dd1SGerd Hoffmann #endif 768f473dd1SGerd Hoffmann 7749ddd7e1SKővágó, Zoltán #define CHECK_SUCCESS_GOTO(c, expression, label, msg) \ 78ea9ebc2cSMarc-André Lureau do { \ 79ea9ebc2cSMarc-André Lureau if (!(expression)) { \ 8049ddd7e1SKővágó, Zoltán qpa_logerr(pa_context_errno((c)->context), msg); \ 81ea9ebc2cSMarc-André Lureau goto label; \ 82ea9ebc2cSMarc-André Lureau } \ 832562755eSEric Blake } while (0) 84ea9ebc2cSMarc-André Lureau 8549ddd7e1SKővágó, Zoltán #define CHECK_DEAD_GOTO(c, stream, label, msg) \ 86ea9ebc2cSMarc-André Lureau do { \ 87ea9ebc2cSMarc-André Lureau if (!(c)->context || !PA_CONTEXT_IS_GOOD (pa_context_get_state((c)->context)) || \ 88ea9ebc2cSMarc-André Lureau !(stream) || !PA_STREAM_IS_GOOD (pa_stream_get_state ((stream)))) { \ 89ea9ebc2cSMarc-André Lureau if (((c)->context && pa_context_get_state ((c)->context) == PA_CONTEXT_FAILED) || \ 90ea9ebc2cSMarc-André Lureau ((stream) && pa_stream_get_state ((stream)) == PA_STREAM_FAILED)) { \ 9149ddd7e1SKővágó, Zoltán qpa_logerr(pa_context_errno((c)->context), msg); \ 92ea9ebc2cSMarc-André Lureau } else { \ 9349ddd7e1SKővágó, Zoltán qpa_logerr(PA_ERR_BADSTATE, msg); \ 94ea9ebc2cSMarc-André Lureau } \ 95ea9ebc2cSMarc-André Lureau goto label; \ 96ea9ebc2cSMarc-André Lureau } \ 972562755eSEric Blake } while (0) 98ea9ebc2cSMarc-André Lureau 99337e8de6SKővágó, Zoltán static void *qpa_get_buffer_in(HWVoiceIn *hw, size_t *size) 100337e8de6SKővágó, Zoltán { 101337e8de6SKővágó, Zoltán PAVoiceIn *p = (PAVoiceIn *) hw; 102337e8de6SKővágó, Zoltán PAConnection *c = p->g->conn; 103337e8de6SKővágó, Zoltán int r; 104337e8de6SKővágó, Zoltán 105337e8de6SKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 106337e8de6SKővágó, Zoltán 107337e8de6SKővágó, Zoltán CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, 108337e8de6SKővágó, Zoltán "pa_threaded_mainloop_lock failed\n"); 109337e8de6SKővágó, Zoltán 110337e8de6SKővágó, Zoltán if (!p->read_length) { 111337e8de6SKővágó, Zoltán r = pa_stream_peek(p->stream, &p->read_data, &p->read_length); 112337e8de6SKővágó, Zoltán CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail, 113337e8de6SKővágó, Zoltán "pa_stream_peek failed\n"); 114337e8de6SKővágó, Zoltán } 115337e8de6SKővágó, Zoltán 116337e8de6SKővágó, Zoltán *size = MIN(p->read_length, *size); 117337e8de6SKővágó, Zoltán 118337e8de6SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 119337e8de6SKővágó, Zoltán return (void *) p->read_data; 120337e8de6SKővágó, Zoltán 121337e8de6SKővágó, Zoltán unlock_and_fail: 122337e8de6SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 123337e8de6SKővágó, Zoltán *size = 0; 124337e8de6SKővágó, Zoltán return NULL; 125337e8de6SKővágó, Zoltán } 126337e8de6SKővágó, Zoltán 127337e8de6SKővágó, Zoltán static void qpa_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size) 128337e8de6SKővágó, Zoltán { 129337e8de6SKővágó, Zoltán PAVoiceIn *p = (PAVoiceIn *) hw; 130337e8de6SKővágó, Zoltán PAConnection *c = p->g->conn; 131337e8de6SKővágó, Zoltán int r; 132337e8de6SKővágó, Zoltán 133337e8de6SKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 134337e8de6SKővágó, Zoltán 135337e8de6SKővágó, Zoltán CHECK_DEAD_GOTO(c, p->stream, unlock, 136337e8de6SKővágó, Zoltán "pa_threaded_mainloop_lock failed\n"); 137337e8de6SKővágó, Zoltán 138337e8de6SKővágó, Zoltán assert(buf == p->read_data && size <= p->read_length); 139337e8de6SKővágó, Zoltán 140337e8de6SKővágó, Zoltán p->read_data += size; 141337e8de6SKővágó, Zoltán p->read_length -= size; 142337e8de6SKővágó, Zoltán 143337e8de6SKővágó, Zoltán if (size && !p->read_length) { 144337e8de6SKővágó, Zoltán r = pa_stream_drop(p->stream); 145337e8de6SKővágó, Zoltán CHECK_SUCCESS_GOTO(c, r == 0, unlock, "pa_stream_drop failed\n"); 146337e8de6SKővágó, Zoltán } 147337e8de6SKővágó, Zoltán 148337e8de6SKővágó, Zoltán unlock: 149337e8de6SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 150337e8de6SKővágó, Zoltán } 151337e8de6SKővágó, Zoltán 15249ddd7e1SKővágó, Zoltán static size_t qpa_read(HWVoiceIn *hw, void *data, size_t length) 153ea9ebc2cSMarc-André Lureau { 15449ddd7e1SKővágó, Zoltán PAVoiceIn *p = (PAVoiceIn *) hw; 1559d34e6d8SKővágó, Zoltán PAConnection *c = p->g->conn; 156acc3b63eSVolker Rümelin size_t total = 0; 157ea9ebc2cSMarc-André Lureau 1589d34e6d8SKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 159ea9ebc2cSMarc-André Lureau 16049ddd7e1SKővágó, Zoltán CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, 16149ddd7e1SKővágó, Zoltán "pa_threaded_mainloop_lock failed\n"); 1627c9eb86eSVolker Rümelin if (pa_stream_get_state(p->stream) != PA_STREAM_READY) { 1637c9eb86eSVolker Rümelin /* wait for stream to become ready */ 1647c9eb86eSVolker Rümelin goto unlock; 1657c9eb86eSVolker Rümelin } 166ea9ebc2cSMarc-André Lureau 167acc3b63eSVolker Rümelin while (total < length) { 168acc3b63eSVolker Rümelin size_t l; 169acc3b63eSVolker Rümelin int r; 170acc3b63eSVolker Rümelin 17149ddd7e1SKővágó, Zoltán if (!p->read_length) { 172ea9ebc2cSMarc-André Lureau r = pa_stream_peek(p->stream, &p->read_data, &p->read_length); 17349ddd7e1SKővágó, Zoltán CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail, 17449ddd7e1SKővágó, Zoltán "pa_stream_peek failed\n"); 175acc3b63eSVolker Rümelin if (!p->read_length) { 176acc3b63eSVolker Rümelin /* buffer is empty */ 177acc3b63eSVolker Rümelin break; 178acc3b63eSVolker Rümelin } 179ea9ebc2cSMarc-André Lureau } 180ea9ebc2cSMarc-André Lureau 181acc3b63eSVolker Rümelin l = MIN(p->read_length, length - total); 182acc3b63eSVolker Rümelin memcpy((char *)data + total, p->read_data, l); 183ea9ebc2cSMarc-André Lureau 18449ddd7e1SKővágó, Zoltán p->read_data += l; 185ea9ebc2cSMarc-André Lureau p->read_length -= l; 186acc3b63eSVolker Rümelin total += l; 187ea9ebc2cSMarc-André Lureau 188ea9ebc2cSMarc-André Lureau if (!p->read_length) { 189ea9ebc2cSMarc-André Lureau r = pa_stream_drop(p->stream); 19049ddd7e1SKővágó, Zoltán CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail, 19149ddd7e1SKővágó, Zoltán "pa_stream_drop failed\n"); 192ea9ebc2cSMarc-André Lureau } 193acc3b63eSVolker Rümelin } 194ea9ebc2cSMarc-André Lureau 1957c9eb86eSVolker Rümelin unlock: 1969d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 197acc3b63eSVolker Rümelin return total; 198ea9ebc2cSMarc-André Lureau 199ea9ebc2cSMarc-André Lureau unlock_and_fail: 2009d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 20149ddd7e1SKővágó, Zoltán return 0; 202ea9ebc2cSMarc-André Lureau } 203ea9ebc2cSMarc-André Lureau 204ddf2050cSVolker Rümelin static size_t qpa_buffer_get_free(HWVoiceOut *hw) 205337e8de6SKővágó, Zoltán { 206337e8de6SKővágó, Zoltán PAVoiceOut *p = (PAVoiceOut *)hw; 207337e8de6SKővágó, Zoltán PAConnection *c = p->g->conn; 208bea29e9fSVolker Rümelin size_t l; 209337e8de6SKővágó, Zoltán 210337e8de6SKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 211337e8de6SKővágó, Zoltán 212337e8de6SKővágó, Zoltán CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, 213337e8de6SKővágó, Zoltán "pa_threaded_mainloop_lock failed\n"); 2147007cd3fSVolker Rümelin if (pa_stream_get_state(p->stream) != PA_STREAM_READY) { 2157007cd3fSVolker Rümelin /* wait for stream to become ready */ 2167007cd3fSVolker Rümelin l = 0; 2177007cd3fSVolker Rümelin goto unlock; 2187007cd3fSVolker Rümelin } 219337e8de6SKővágó, Zoltán 220bea29e9fSVolker Rümelin l = pa_stream_writable_size(p->stream); 221bea29e9fSVolker Rümelin CHECK_SUCCESS_GOTO(c, l != (size_t) -1, unlock_and_fail, 222bea29e9fSVolker Rümelin "pa_stream_writable_size failed\n"); 223bea29e9fSVolker Rümelin 224ddf2050cSVolker Rümelin unlock: 225ddf2050cSVolker Rümelin pa_threaded_mainloop_unlock(c->mainloop); 226ddf2050cSVolker Rümelin return l; 227ddf2050cSVolker Rümelin 228ddf2050cSVolker Rümelin unlock_and_fail: 229ddf2050cSVolker Rümelin pa_threaded_mainloop_unlock(c->mainloop); 230ddf2050cSVolker Rümelin return 0; 231ddf2050cSVolker Rümelin } 232ddf2050cSVolker Rümelin 233ddf2050cSVolker Rümelin static void *qpa_get_buffer_out(HWVoiceOut *hw, size_t *size) 234ddf2050cSVolker Rümelin { 235ddf2050cSVolker Rümelin PAVoiceOut *p = (PAVoiceOut *)hw; 236ddf2050cSVolker Rümelin PAConnection *c = p->g->conn; 237ddf2050cSVolker Rümelin void *ret; 238ddf2050cSVolker Rümelin int r; 239ddf2050cSVolker Rümelin 240ddf2050cSVolker Rümelin pa_threaded_mainloop_lock(c->mainloop); 241ddf2050cSVolker Rümelin 242ddf2050cSVolker Rümelin CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, 243ddf2050cSVolker Rümelin "pa_threaded_mainloop_lock failed\n"); 244ddf2050cSVolker Rümelin 245337e8de6SKővágó, Zoltán *size = -1; 246337e8de6SKővágó, Zoltán r = pa_stream_begin_write(p->stream, &ret, size); 247337e8de6SKővágó, Zoltán CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail, 248337e8de6SKővágó, Zoltán "pa_stream_begin_write failed\n"); 249337e8de6SKővágó, Zoltán 250337e8de6SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 251337e8de6SKővágó, Zoltán return ret; 252337e8de6SKővágó, Zoltán 253337e8de6SKővágó, Zoltán unlock_and_fail: 254337e8de6SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 255337e8de6SKővágó, Zoltán *size = 0; 256337e8de6SKővágó, Zoltán return NULL; 257337e8de6SKővágó, Zoltán } 258337e8de6SKővágó, Zoltán 259bea29e9fSVolker Rümelin static size_t qpa_put_buffer_out(HWVoiceOut *hw, void *data, size_t length) 260bea29e9fSVolker Rümelin { 261bea29e9fSVolker Rümelin PAVoiceOut *p = (PAVoiceOut *)hw; 262bea29e9fSVolker Rümelin PAConnection *c = p->g->conn; 263bea29e9fSVolker Rümelin int r; 264bea29e9fSVolker Rümelin 265bea29e9fSVolker Rümelin pa_threaded_mainloop_lock(c->mainloop); 266bea29e9fSVolker Rümelin 267bea29e9fSVolker Rümelin CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, 268bea29e9fSVolker Rümelin "pa_threaded_mainloop_lock failed\n"); 269bea29e9fSVolker Rümelin 270bea29e9fSVolker Rümelin r = pa_stream_write(p->stream, data, length, NULL, 0LL, PA_SEEK_RELATIVE); 271bea29e9fSVolker Rümelin CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail, "pa_stream_write failed\n"); 272bea29e9fSVolker Rümelin 273bea29e9fSVolker Rümelin pa_threaded_mainloop_unlock(c->mainloop); 274bea29e9fSVolker Rümelin return length; 275bea29e9fSVolker Rümelin 276bea29e9fSVolker Rümelin unlock_and_fail: 277bea29e9fSVolker Rümelin pa_threaded_mainloop_unlock(c->mainloop); 278bea29e9fSVolker Rümelin return 0; 279bea29e9fSVolker Rümelin } 280bea29e9fSVolker Rümelin 28149ddd7e1SKővágó, Zoltán static size_t qpa_write(HWVoiceOut *hw, void *data, size_t length) 282ea9ebc2cSMarc-André Lureau { 28349ddd7e1SKővágó, Zoltán PAVoiceOut *p = (PAVoiceOut *) hw; 2849d34e6d8SKővágó, Zoltán PAConnection *c = p->g->conn; 285ea9ebc2cSMarc-André Lureau size_t l; 286ea9ebc2cSMarc-André Lureau int r; 287ea9ebc2cSMarc-André Lureau 28849ddd7e1SKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 289ea9ebc2cSMarc-André Lureau 29049ddd7e1SKővágó, Zoltán CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, 29149ddd7e1SKővágó, Zoltán "pa_threaded_mainloop_lock failed\n"); 292e270c548SVolker Rümelin if (pa_stream_get_state(p->stream) != PA_STREAM_READY) { 293e270c548SVolker Rümelin /* wait for stream to become ready */ 294e270c548SVolker Rümelin l = 0; 295e270c548SVolker Rümelin goto unlock; 296e270c548SVolker Rümelin } 29749ddd7e1SKővágó, Zoltán 29849ddd7e1SKővágó, Zoltán l = pa_stream_writable_size(p->stream); 29949ddd7e1SKővágó, Zoltán 30049ddd7e1SKővágó, Zoltán CHECK_SUCCESS_GOTO(c, l != (size_t) -1, unlock_and_fail, 30149ddd7e1SKővágó, Zoltán "pa_stream_writable_size failed\n"); 302ea9ebc2cSMarc-André Lureau 303ea9ebc2cSMarc-André Lureau if (l > length) { 304ea9ebc2cSMarc-André Lureau l = length; 305ea9ebc2cSMarc-André Lureau } 306ea9ebc2cSMarc-André Lureau 307ea9ebc2cSMarc-André Lureau r = pa_stream_write(p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE); 30849ddd7e1SKővágó, Zoltán CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail, "pa_stream_write failed\n"); 309ea9ebc2cSMarc-André Lureau 310e270c548SVolker Rümelin unlock: 3119d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 31249ddd7e1SKővágó, Zoltán return l; 313ea9ebc2cSMarc-André Lureau 314ea9ebc2cSMarc-André Lureau unlock_and_fail: 3159d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 316b8e59f18Smalc return 0; 317b8e59f18Smalc } 318b8e59f18Smalc 31985bc5852SKővágó, Zoltán static pa_sample_format_t audfmt_to_pa (AudioFormat afmt, int endianness) 320b8e59f18Smalc { 321b8e59f18Smalc int format; 322b8e59f18Smalc 323b8e59f18Smalc switch (afmt) { 32485bc5852SKővágó, Zoltán case AUDIO_FORMAT_S8: 32585bc5852SKővágó, Zoltán case AUDIO_FORMAT_U8: 326b8e59f18Smalc format = PA_SAMPLE_U8; 327b8e59f18Smalc break; 32885bc5852SKővágó, Zoltán case AUDIO_FORMAT_S16: 32985bc5852SKővágó, Zoltán case AUDIO_FORMAT_U16: 330b8e59f18Smalc format = endianness ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE; 331b8e59f18Smalc break; 33285bc5852SKővágó, Zoltán case AUDIO_FORMAT_S32: 33385bc5852SKővágó, Zoltán case AUDIO_FORMAT_U32: 334b8e59f18Smalc format = endianness ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE; 335b8e59f18Smalc break; 336ed2a4a79SKővágó, Zoltán case AUDIO_FORMAT_F32: 337ed2a4a79SKővágó, Zoltán format = endianness ? PA_SAMPLE_FLOAT32BE : PA_SAMPLE_FLOAT32LE; 338ed2a4a79SKővágó, Zoltán break; 339b8e59f18Smalc default: 340b8e59f18Smalc dolog ("Internal logic error: Bad audio format %d\n", afmt); 341b8e59f18Smalc format = PA_SAMPLE_U8; 342b8e59f18Smalc break; 343b8e59f18Smalc } 344b8e59f18Smalc return format; 345b8e59f18Smalc } 346b8e59f18Smalc 34785bc5852SKővágó, Zoltán static AudioFormat pa_to_audfmt (pa_sample_format_t fmt, int *endianness) 348b8e59f18Smalc { 349b8e59f18Smalc switch (fmt) { 350b8e59f18Smalc case PA_SAMPLE_U8: 35185bc5852SKővágó, Zoltán return AUDIO_FORMAT_U8; 352b8e59f18Smalc case PA_SAMPLE_S16BE: 353b8e59f18Smalc *endianness = 1; 35485bc5852SKővágó, Zoltán return AUDIO_FORMAT_S16; 355b8e59f18Smalc case PA_SAMPLE_S16LE: 356b8e59f18Smalc *endianness = 0; 35785bc5852SKővágó, Zoltán return AUDIO_FORMAT_S16; 358b8e59f18Smalc case PA_SAMPLE_S32BE: 359b8e59f18Smalc *endianness = 1; 36085bc5852SKővágó, Zoltán return AUDIO_FORMAT_S32; 361b8e59f18Smalc case PA_SAMPLE_S32LE: 362b8e59f18Smalc *endianness = 0; 36385bc5852SKővágó, Zoltán return AUDIO_FORMAT_S32; 364ed2a4a79SKővágó, Zoltán case PA_SAMPLE_FLOAT32BE: 365ed2a4a79SKővágó, Zoltán *endianness = 1; 366ed2a4a79SKővágó, Zoltán return AUDIO_FORMAT_F32; 367ed2a4a79SKővágó, Zoltán case PA_SAMPLE_FLOAT32LE: 368ed2a4a79SKővágó, Zoltán *endianness = 0; 369ed2a4a79SKővágó, Zoltán return AUDIO_FORMAT_F32; 370b8e59f18Smalc default: 371b8e59f18Smalc dolog ("Internal logic error: Bad pa_sample_format %d\n", fmt); 37285bc5852SKővágó, Zoltán return AUDIO_FORMAT_U8; 373b8e59f18Smalc } 374b8e59f18Smalc } 375b8e59f18Smalc 376ea9ebc2cSMarc-André Lureau static void context_state_cb (pa_context *c, void *userdata) 377ea9ebc2cSMarc-André Lureau { 3789d34e6d8SKővágó, Zoltán PAConnection *conn = userdata; 379ea9ebc2cSMarc-André Lureau 380ea9ebc2cSMarc-André Lureau switch (pa_context_get_state(c)) { 381ea9ebc2cSMarc-André Lureau case PA_CONTEXT_READY: 382ea9ebc2cSMarc-André Lureau case PA_CONTEXT_TERMINATED: 383ea9ebc2cSMarc-André Lureau case PA_CONTEXT_FAILED: 3849d34e6d8SKővágó, Zoltán pa_threaded_mainloop_signal(conn->mainloop, 0); 385ea9ebc2cSMarc-André Lureau break; 386ea9ebc2cSMarc-André Lureau 387ea9ebc2cSMarc-André Lureau case PA_CONTEXT_UNCONNECTED: 388ea9ebc2cSMarc-André Lureau case PA_CONTEXT_CONNECTING: 389ea9ebc2cSMarc-André Lureau case PA_CONTEXT_AUTHORIZING: 390ea9ebc2cSMarc-André Lureau case PA_CONTEXT_SETTING_NAME: 391ea9ebc2cSMarc-André Lureau break; 392ea9ebc2cSMarc-André Lureau } 393ea9ebc2cSMarc-André Lureau } 394ea9ebc2cSMarc-André Lureau 395ea9ebc2cSMarc-André Lureau static void stream_state_cb (pa_stream *s, void * userdata) 396ea9ebc2cSMarc-André Lureau { 3979d34e6d8SKővágó, Zoltán PAConnection *c = userdata; 398ea9ebc2cSMarc-André Lureau 399ea9ebc2cSMarc-André Lureau switch (pa_stream_get_state (s)) { 400ea9ebc2cSMarc-André Lureau 401ea9ebc2cSMarc-André Lureau case PA_STREAM_READY: 402ea9ebc2cSMarc-André Lureau case PA_STREAM_FAILED: 403ea9ebc2cSMarc-André Lureau case PA_STREAM_TERMINATED: 4049d34e6d8SKővágó, Zoltán pa_threaded_mainloop_signal(c->mainloop, 0); 405ea9ebc2cSMarc-André Lureau break; 406ea9ebc2cSMarc-André Lureau 407ea9ebc2cSMarc-André Lureau case PA_STREAM_UNCONNECTED: 408ea9ebc2cSMarc-André Lureau case PA_STREAM_CREATING: 409ea9ebc2cSMarc-André Lureau break; 410ea9ebc2cSMarc-André Lureau } 411ea9ebc2cSMarc-André Lureau } 412ea9ebc2cSMarc-André Lureau 413ea9ebc2cSMarc-André Lureau static pa_stream *qpa_simple_new ( 4149d34e6d8SKővágó, Zoltán PAConnection *c, 415ea9ebc2cSMarc-André Lureau const char *name, 416ea9ebc2cSMarc-André Lureau pa_stream_direction_t dir, 417ea9ebc2cSMarc-André Lureau const char *dev, 418ea9ebc2cSMarc-André Lureau const pa_sample_spec *ss, 419ea9ebc2cSMarc-André Lureau const pa_buffer_attr *attr, 420ea9ebc2cSMarc-André Lureau int *rerror) 421ea9ebc2cSMarc-André Lureau { 422ea9ebc2cSMarc-André Lureau int r; 4230cf13e36SKővágó, Zoltán pa_stream *stream = NULL; 4249d34e6d8SKővágó, Zoltán pa_stream_flags_t flags; 4250cf13e36SKővágó, Zoltán pa_channel_map map; 426ea9ebc2cSMarc-André Lureau 4279d34e6d8SKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 428ea9ebc2cSMarc-André Lureau 4290cf13e36SKővágó, Zoltán pa_channel_map_init(&map); 4300cf13e36SKővágó, Zoltán map.channels = ss->channels; 4310cf13e36SKővágó, Zoltán 4320cf13e36SKővágó, Zoltán /* 4330cf13e36SKővágó, Zoltán * TODO: This currently expects the only frontend supporting more than 2 4340cf13e36SKővágó, Zoltán * channels is the usb-audio. We will need some means to set channel 4350cf13e36SKővágó, Zoltán * order when a new frontend gains multi-channel support. 4360cf13e36SKővágó, Zoltán */ 4370cf13e36SKővágó, Zoltán switch (ss->channels) { 4380cf13e36SKővágó, Zoltán case 1: 4390cf13e36SKővágó, Zoltán map.map[0] = PA_CHANNEL_POSITION_MONO; 4400cf13e36SKővágó, Zoltán break; 4410cf13e36SKővágó, Zoltán 4420cf13e36SKővágó, Zoltán case 2: 4430cf13e36SKővágó, Zoltán map.map[0] = PA_CHANNEL_POSITION_LEFT; 4440cf13e36SKővágó, Zoltán map.map[1] = PA_CHANNEL_POSITION_RIGHT; 4450cf13e36SKővágó, Zoltán break; 4460cf13e36SKővágó, Zoltán 4470cf13e36SKővágó, Zoltán case 6: 4480cf13e36SKővágó, Zoltán map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; 4490cf13e36SKővágó, Zoltán map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; 4500cf13e36SKővágó, Zoltán map.map[2] = PA_CHANNEL_POSITION_CENTER; 4510cf13e36SKővágó, Zoltán map.map[3] = PA_CHANNEL_POSITION_LFE; 4520cf13e36SKővágó, Zoltán map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT; 4530cf13e36SKővágó, Zoltán map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT; 4540cf13e36SKővágó, Zoltán break; 4550cf13e36SKővágó, Zoltán 4560cf13e36SKővágó, Zoltán case 8: 4570cf13e36SKővágó, Zoltán map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; 4580cf13e36SKővágó, Zoltán map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; 4590cf13e36SKővágó, Zoltán map.map[2] = PA_CHANNEL_POSITION_CENTER; 4600cf13e36SKővágó, Zoltán map.map[3] = PA_CHANNEL_POSITION_LFE; 4610cf13e36SKővágó, Zoltán map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT; 4620cf13e36SKővágó, Zoltán map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT; 4630cf13e36SKővágó, Zoltán map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; 4640cf13e36SKővágó, Zoltán map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; 46556089565SPaolo Bonzini break; 4660cf13e36SKővágó, Zoltán 4670cf13e36SKővágó, Zoltán default: 4680cf13e36SKővágó, Zoltán dolog("Internal error: unsupported channel count %d\n", ss->channels); 4690cf13e36SKővágó, Zoltán goto fail; 4700cf13e36SKővágó, Zoltán } 4710cf13e36SKővágó, Zoltán 4720cf13e36SKővágó, Zoltán stream = pa_stream_new(c->context, name, ss, &map); 473ea9ebc2cSMarc-André Lureau if (!stream) { 474ea9ebc2cSMarc-André Lureau goto fail; 475ea9ebc2cSMarc-André Lureau } 476ea9ebc2cSMarc-André Lureau 4779d34e6d8SKővágó, Zoltán pa_stream_set_state_callback(stream, stream_state_cb, c); 4789d34e6d8SKővágó, Zoltán 47950db82d8SVolker Rümelin flags = PA_STREAM_EARLY_REQUESTS; 480ea9ebc2cSMarc-André Lureau 4818a435f74SKővágó, Zoltán if (dev) { 4828a435f74SKővágó, Zoltán /* don't move the stream if the user specified a sink/source */ 4838a435f74SKővágó, Zoltán flags |= PA_STREAM_DONT_MOVE; 4848a435f74SKővágó, Zoltán } 4858a435f74SKővágó, Zoltán 486ea9ebc2cSMarc-André Lureau if (dir == PA_STREAM_PLAYBACK) { 4879d34e6d8SKővágó, Zoltán r = pa_stream_connect_playback(stream, dev, attr, flags, NULL, NULL); 488ea9ebc2cSMarc-André Lureau } else { 4899d34e6d8SKővágó, Zoltán r = pa_stream_connect_record(stream, dev, attr, flags); 490ea9ebc2cSMarc-André Lureau } 491ea9ebc2cSMarc-André Lureau 492ea9ebc2cSMarc-André Lureau if (r < 0) { 493ea9ebc2cSMarc-André Lureau goto fail; 494ea9ebc2cSMarc-André Lureau } 495ea9ebc2cSMarc-André Lureau 4969d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 497ea9ebc2cSMarc-André Lureau 498ea9ebc2cSMarc-André Lureau return stream; 499ea9ebc2cSMarc-André Lureau 500ea9ebc2cSMarc-André Lureau fail: 5019d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 502ea9ebc2cSMarc-André Lureau 503ea9ebc2cSMarc-André Lureau if (stream) { 504ea9ebc2cSMarc-André Lureau pa_stream_unref (stream); 505ea9ebc2cSMarc-André Lureau } 506ea9ebc2cSMarc-André Lureau 5079d34e6d8SKővágó, Zoltán *rerror = pa_context_errno(c->context); 508ea9ebc2cSMarc-André Lureau 509ea9ebc2cSMarc-André Lureau return NULL; 510ea9ebc2cSMarc-André Lureau } 511ea9ebc2cSMarc-André Lureau 5125706db1dSKővágó, Zoltán static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, 5135706db1dSKővágó, Zoltán void *drv_opaque) 514b8e59f18Smalc { 515b8e59f18Smalc int error; 5169a644c4bSKővágó, Zoltán pa_sample_spec ss; 5179a644c4bSKővágó, Zoltán pa_buffer_attr ba; 5181ea879e5Smalc struct audsettings obt_as = *as; 519b8e59f18Smalc PAVoiceOut *pa = (PAVoiceOut *) hw; 5209a644c4bSKővágó, Zoltán paaudio *g = pa->g = drv_opaque; 5212c324b28SKővágó, Zoltán AudiodevPaOptions *popts = &g->dev->u.pa; 5222c324b28SKővágó, Zoltán AudiodevPaPerDirectionOptions *ppdo = popts->out; 5239d34e6d8SKővágó, Zoltán PAConnection *c = g->conn; 524b8e59f18Smalc 525b8e59f18Smalc ss.format = audfmt_to_pa (as->fmt, as->endianness); 526b8e59f18Smalc ss.channels = as->nchannels; 527b8e59f18Smalc ss.rate = as->freq; 528b8e59f18Smalc 529f6142777SMartin Schrodt ba.tlength = pa_usec_to_bytes(ppdo->latency, &ss); 53000413ed9SVolker Rümelin ba.minreq = pa_usec_to_bytes(MIN(ppdo->latency >> 2, 53100413ed9SVolker Rümelin (g->dev->timer_period >> 2) * 3), &ss); 532e6d16fa4SGerd Hoffmann ba.maxlength = -1; 533e6d16fa4SGerd Hoffmann ba.prebuf = -1; 534e6d16fa4SGerd Hoffmann 535b8e59f18Smalc obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); 536b8e59f18Smalc 537ea9ebc2cSMarc-André Lureau pa->stream = qpa_simple_new ( 5389d34e6d8SKővágó, Zoltán c, 539f47dffe8SKővágó, Zoltán ppdo->has_stream_name ? ppdo->stream_name : g->dev->id, 540b8e59f18Smalc PA_STREAM_PLAYBACK, 5412c324b28SKővágó, Zoltán ppdo->has_name ? ppdo->name : NULL, 542b8e59f18Smalc &ss, 543e6d16fa4SGerd Hoffmann &ba, /* buffering attributes */ 544b8e59f18Smalc &error 545b8e59f18Smalc ); 546ea9ebc2cSMarc-André Lureau if (!pa->stream) { 547b8e59f18Smalc qpa_logerr (error, "pa_simple_new for playback failed\n"); 548b8e59f18Smalc goto fail1; 549b8e59f18Smalc } 550b8e59f18Smalc 551b8e59f18Smalc audio_pcm_init_info (&hw->info, &obt_as); 552acf7a705SVolker Rümelin /* hw->samples counts in frames */ 553acf7a705SVolker Rümelin hw->samples = audio_buffer_frames( 554521ce714SVolker Rümelin qapi_AudiodevPaPerDirectionOptions_base(ppdo), &obt_as, 46440); 555b8e59f18Smalc 556b8e59f18Smalc return 0; 557b8e59f18Smalc 558b8e59f18Smalc fail1: 559b8e59f18Smalc return -1; 560b8e59f18Smalc } 561b8e59f18Smalc 5625706db1dSKővágó, Zoltán static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) 563b8e59f18Smalc { 564b8e59f18Smalc int error; 5659a644c4bSKővágó, Zoltán pa_sample_spec ss; 566ade10301SMartin Schrodt pa_buffer_attr ba; 5671ea879e5Smalc struct audsettings obt_as = *as; 568b8e59f18Smalc PAVoiceIn *pa = (PAVoiceIn *) hw; 5699a644c4bSKővágó, Zoltán paaudio *g = pa->g = drv_opaque; 5702c324b28SKővágó, Zoltán AudiodevPaOptions *popts = &g->dev->u.pa; 5712c324b28SKővágó, Zoltán AudiodevPaPerDirectionOptions *ppdo = popts->in; 5729d34e6d8SKővágó, Zoltán PAConnection *c = g->conn; 573b8e59f18Smalc 574b8e59f18Smalc ss.format = audfmt_to_pa (as->fmt, as->endianness); 575b8e59f18Smalc ss.channels = as->nchannels; 576b8e59f18Smalc ss.rate = as->freq; 577b8e59f18Smalc 578d9a8b27aSVolker Rümelin ba.fragsize = pa_usec_to_bytes((g->dev->timer_period >> 1) * 3, &ss); 579d9a8b27aSVolker Rümelin ba.maxlength = pa_usec_to_bytes( 580d9a8b27aSVolker Rümelin MAX(ppdo->latency, g->dev->timer_period * 3), &ss); 581ade10301SMartin Schrodt ba.minreq = -1; 582ade10301SMartin Schrodt ba.prebuf = -1; 583ade10301SMartin Schrodt 584b8e59f18Smalc obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); 585b8e59f18Smalc 586ea9ebc2cSMarc-André Lureau pa->stream = qpa_simple_new ( 5879d34e6d8SKővágó, Zoltán c, 588f47dffe8SKővágó, Zoltán ppdo->has_stream_name ? ppdo->stream_name : g->dev->id, 589b8e59f18Smalc PA_STREAM_RECORD, 5902c324b28SKővágó, Zoltán ppdo->has_name ? ppdo->name : NULL, 591b8e59f18Smalc &ss, 592ade10301SMartin Schrodt &ba, /* buffering attributes */ 593b8e59f18Smalc &error 594b8e59f18Smalc ); 595ea9ebc2cSMarc-André Lureau if (!pa->stream) { 596b8e59f18Smalc qpa_logerr (error, "pa_simple_new for capture failed\n"); 597b8e59f18Smalc goto fail1; 598b8e59f18Smalc } 599b8e59f18Smalc 600b8e59f18Smalc audio_pcm_init_info (&hw->info, &obt_as); 601acf7a705SVolker Rümelin /* hw->samples counts in frames */ 602acf7a705SVolker Rümelin hw->samples = audio_buffer_frames( 603521ce714SVolker Rümelin qapi_AudiodevPaPerDirectionOptions_base(ppdo), &obt_as, 46440); 604b8e59f18Smalc 605b8e59f18Smalc return 0; 606b8e59f18Smalc 607b8e59f18Smalc fail1: 608b8e59f18Smalc return -1; 609b8e59f18Smalc } 610b8e59f18Smalc 6118692bf7dSKővágó, Zoltán static void qpa_simple_disconnect(PAConnection *c, pa_stream *stream) 6128692bf7dSKővágó, Zoltán { 6138692bf7dSKővágó, Zoltán int err; 6148692bf7dSKővágó, Zoltán 6158692bf7dSKővágó, Zoltán /* 6168692bf7dSKővágó, Zoltán * wait until actually connects. workaround pa bug #247 6178692bf7dSKővágó, Zoltán * https://gitlab.freedesktop.org/pulseaudio/pulseaudio/issues/247 6188692bf7dSKővágó, Zoltán */ 6198692bf7dSKővágó, Zoltán while (pa_stream_get_state(stream) == PA_STREAM_CREATING) { 6208692bf7dSKővágó, Zoltán pa_threaded_mainloop_wait(c->mainloop); 6218692bf7dSKővágó, Zoltán } 6228692bf7dSKővágó, Zoltán 6238692bf7dSKővágó, Zoltán err = pa_stream_disconnect(stream); 6248692bf7dSKővágó, Zoltán if (err != 0) { 6258692bf7dSKővágó, Zoltán dolog("Failed to disconnect! err=%d\n", err); 6268692bf7dSKővágó, Zoltán } 6278692bf7dSKővágó, Zoltán pa_stream_unref(stream); 6288692bf7dSKővágó, Zoltán } 6298692bf7dSKővágó, Zoltán 630b8e59f18Smalc static void qpa_fini_out (HWVoiceOut *hw) 631b8e59f18Smalc { 632b8e59f18Smalc PAVoiceOut *pa = (PAVoiceOut *) hw; 633b8e59f18Smalc 634ea9ebc2cSMarc-André Lureau if (pa->stream) { 6354db3e634SVolker Rümelin PAConnection *c = pa->g->conn; 6364db3e634SVolker Rümelin 6374db3e634SVolker Rümelin pa_threaded_mainloop_lock(c->mainloop); 6384db3e634SVolker Rümelin qpa_simple_disconnect(c, pa->stream); 639ea9ebc2cSMarc-André Lureau pa->stream = NULL; 6404db3e634SVolker Rümelin pa_threaded_mainloop_unlock(c->mainloop); 641b8e59f18Smalc } 642b8e59f18Smalc } 643b8e59f18Smalc 644b8e59f18Smalc static void qpa_fini_in (HWVoiceIn *hw) 645b8e59f18Smalc { 646b8e59f18Smalc PAVoiceIn *pa = (PAVoiceIn *) hw; 647b8e59f18Smalc 648ea9ebc2cSMarc-André Lureau if (pa->stream) { 6494db3e634SVolker Rümelin PAConnection *c = pa->g->conn; 6504db3e634SVolker Rümelin 6514db3e634SVolker Rümelin pa_threaded_mainloop_lock(c->mainloop); 6524db3e634SVolker Rümelin if (pa->read_length) { 6534db3e634SVolker Rümelin int r = pa_stream_drop(pa->stream); 6544db3e634SVolker Rümelin if (r) { 6554db3e634SVolker Rümelin qpa_logerr(pa_context_errno(c->context), 6564db3e634SVolker Rümelin "pa_stream_drop failed\n"); 6574db3e634SVolker Rümelin } 6584db3e634SVolker Rümelin pa->read_length = 0; 6594db3e634SVolker Rümelin } 6604db3e634SVolker Rümelin qpa_simple_disconnect(c, pa->stream); 661ea9ebc2cSMarc-André Lureau pa->stream = NULL; 6624db3e634SVolker Rümelin pa_threaded_mainloop_unlock(c->mainloop); 663b8e59f18Smalc } 664b8e59f18Smalc } 665b8e59f18Smalc 666cecc1e79SKővágó, Zoltán static void qpa_volume_out(HWVoiceOut *hw, Volume *vol) 667b8e59f18Smalc { 6686e7a7f3dSMarc-André Lureau PAVoiceOut *pa = (PAVoiceOut *) hw; 6696e7a7f3dSMarc-André Lureau pa_operation *op; 6706e7a7f3dSMarc-André Lureau pa_cvolume v; 6719d34e6d8SKővágó, Zoltán PAConnection *c = pa->g->conn; 672cecc1e79SKővágó, Zoltán int i; 6736e7a7f3dSMarc-André Lureau 6748f473dd1SGerd Hoffmann #ifdef PA_CHECK_VERSION /* macro is present in 0.9.16+ */ 6758f473dd1SGerd Hoffmann pa_cvolume_init (&v); /* function is present in 0.9.13+ */ 6768f473dd1SGerd Hoffmann #endif 6776e7a7f3dSMarc-André Lureau 678cecc1e79SKővágó, Zoltán v.channels = vol->channels; 679cecc1e79SKővágó, Zoltán for (i = 0; i < vol->channels; ++i) { 680cecc1e79SKővágó, Zoltán v.values[i] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->vol[i]) / 255; 681cecc1e79SKővágó, Zoltán } 6826e7a7f3dSMarc-André Lureau 6839d34e6d8SKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 6846e7a7f3dSMarc-André Lureau 6859d34e6d8SKővágó, Zoltán op = pa_context_set_sink_input_volume(c->context, 6866e7a7f3dSMarc-André Lureau pa_stream_get_index(pa->stream), 6876e7a7f3dSMarc-André Lureau &v, NULL, NULL); 6889d34e6d8SKővágó, Zoltán if (!op) { 6899d34e6d8SKővágó, Zoltán qpa_logerr(pa_context_errno(c->context), 6906e7a7f3dSMarc-André Lureau "set_sink_input_volume() failed\n"); 6919d34e6d8SKővágó, Zoltán } else { 6926e7a7f3dSMarc-André Lureau pa_operation_unref(op); 6939d34e6d8SKővágó, Zoltán } 6946e7a7f3dSMarc-André Lureau 6959d34e6d8SKővágó, Zoltán op = pa_context_set_sink_input_mute(c->context, 6966e7a7f3dSMarc-André Lureau pa_stream_get_index(pa->stream), 697571a8c52SKővágó, Zoltán vol->mute, NULL, NULL); 6986e7a7f3dSMarc-André Lureau if (!op) { 6999d34e6d8SKővágó, Zoltán qpa_logerr(pa_context_errno(c->context), 7006e7a7f3dSMarc-André Lureau "set_sink_input_mute() failed\n"); 7016e7a7f3dSMarc-André Lureau } else { 7026e7a7f3dSMarc-André Lureau pa_operation_unref(op); 7036e7a7f3dSMarc-André Lureau } 7046e7a7f3dSMarc-André Lureau 7059d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 7066e7a7f3dSMarc-André Lureau } 707b8e59f18Smalc 708cecc1e79SKővágó, Zoltán static void qpa_volume_in(HWVoiceIn *hw, Volume *vol) 709b8e59f18Smalc { 7106e7a7f3dSMarc-André Lureau PAVoiceIn *pa = (PAVoiceIn *) hw; 7116e7a7f3dSMarc-André Lureau pa_operation *op; 7126e7a7f3dSMarc-André Lureau pa_cvolume v; 7139d34e6d8SKővágó, Zoltán PAConnection *c = pa->g->conn; 714cecc1e79SKővágó, Zoltán int i; 7156e7a7f3dSMarc-André Lureau 7168f473dd1SGerd Hoffmann #ifdef PA_CHECK_VERSION 7176e7a7f3dSMarc-André Lureau pa_cvolume_init (&v); 7188f473dd1SGerd Hoffmann #endif 7196e7a7f3dSMarc-André Lureau 720cecc1e79SKővágó, Zoltán v.channels = vol->channels; 721cecc1e79SKővágó, Zoltán for (i = 0; i < vol->channels; ++i) { 722cecc1e79SKővágó, Zoltán v.values[i] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->vol[i]) / 255; 723cecc1e79SKővágó, Zoltán } 7246e7a7f3dSMarc-André Lureau 7259d34e6d8SKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 7266e7a7f3dSMarc-André Lureau 7279d34e6d8SKővágó, Zoltán op = pa_context_set_source_output_volume(c->context, 728e58ff62dSPeter Krempa pa_stream_get_index(pa->stream), 7296e7a7f3dSMarc-André Lureau &v, NULL, NULL); 7306e7a7f3dSMarc-André Lureau if (!op) { 7319d34e6d8SKővágó, Zoltán qpa_logerr(pa_context_errno(c->context), 732e58ff62dSPeter Krempa "set_source_output_volume() failed\n"); 7336e7a7f3dSMarc-André Lureau } else { 7346e7a7f3dSMarc-André Lureau pa_operation_unref(op); 7356e7a7f3dSMarc-André Lureau } 7366e7a7f3dSMarc-André Lureau 7379d34e6d8SKővágó, Zoltán op = pa_context_set_source_output_mute(c->context, 7386e7a7f3dSMarc-André Lureau pa_stream_get_index(pa->stream), 739571a8c52SKővágó, Zoltán vol->mute, NULL, NULL); 7406e7a7f3dSMarc-André Lureau if (!op) { 7419d34e6d8SKővágó, Zoltán qpa_logerr(pa_context_errno(c->context), 742e58ff62dSPeter Krempa "set_source_output_mute() failed\n"); 7436e7a7f3dSMarc-André Lureau } else { 7446e7a7f3dSMarc-André Lureau pa_operation_unref(op); 7456e7a7f3dSMarc-André Lureau } 7466e7a7f3dSMarc-André Lureau 7479d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 7486e7a7f3dSMarc-André Lureau } 749b8e59f18Smalc 750baea032eSMartin Schrodt static int qpa_validate_per_direction_opts(Audiodev *dev, 751baea032eSMartin Schrodt AudiodevPaPerDirectionOptions *pdo) 752baea032eSMartin Schrodt { 753f6142777SMartin Schrodt if (!pdo->has_latency) { 754f6142777SMartin Schrodt pdo->has_latency = true; 75530ff5e24SVolker Rümelin pdo->latency = 46440; 756f6142777SMartin Schrodt } 757baea032eSMartin Schrodt return 1; 758baea032eSMartin Schrodt } 759baea032eSMartin Schrodt 7609d34e6d8SKővágó, Zoltán /* common */ 7619d34e6d8SKővágó, Zoltán static void *qpa_conn_init(const char *server) 7629d34e6d8SKővágó, Zoltán { 763*b21e2380SMarkus Armbruster PAConnection *c = g_new0(PAConnection, 1); 7649d34e6d8SKővágó, Zoltán QTAILQ_INSERT_TAIL(&pa_conns, c, list); 7659d34e6d8SKővágó, Zoltán 7669d34e6d8SKővágó, Zoltán c->mainloop = pa_threaded_mainloop_new(); 7679d34e6d8SKővágó, Zoltán if (!c->mainloop) { 7689d34e6d8SKővágó, Zoltán goto fail; 7699d34e6d8SKővágó, Zoltán } 7709d34e6d8SKővágó, Zoltán 7719d34e6d8SKővágó, Zoltán c->context = pa_context_new(pa_threaded_mainloop_get_api(c->mainloop), 77237a54d05SVolker Rümelin audio_application_name()); 7739d34e6d8SKővágó, Zoltán if (!c->context) { 7749d34e6d8SKővágó, Zoltán goto fail; 7759d34e6d8SKővágó, Zoltán } 7769d34e6d8SKővágó, Zoltán 7779d34e6d8SKővágó, Zoltán pa_context_set_state_callback(c->context, context_state_cb, c); 7789d34e6d8SKővágó, Zoltán 7799d34e6d8SKővágó, Zoltán if (pa_context_connect(c->context, server, 0, NULL) < 0) { 7809d34e6d8SKővágó, Zoltán qpa_logerr(pa_context_errno(c->context), 7819d34e6d8SKővágó, Zoltán "pa_context_connect() failed\n"); 7829d34e6d8SKővágó, Zoltán goto fail; 7839d34e6d8SKővágó, Zoltán } 7849d34e6d8SKővágó, Zoltán 7859d34e6d8SKővágó, Zoltán pa_threaded_mainloop_lock(c->mainloop); 7869d34e6d8SKővágó, Zoltán 7879d34e6d8SKővágó, Zoltán if (pa_threaded_mainloop_start(c->mainloop) < 0) { 7889d34e6d8SKővágó, Zoltán goto unlock_and_fail; 7899d34e6d8SKővágó, Zoltán } 7909d34e6d8SKővágó, Zoltán 7919d34e6d8SKővágó, Zoltán for (;;) { 7929d34e6d8SKővágó, Zoltán pa_context_state_t state; 7939d34e6d8SKővágó, Zoltán 7949d34e6d8SKővágó, Zoltán state = pa_context_get_state(c->context); 7959d34e6d8SKővágó, Zoltán 7969d34e6d8SKővágó, Zoltán if (state == PA_CONTEXT_READY) { 7979d34e6d8SKővágó, Zoltán break; 7989d34e6d8SKővágó, Zoltán } 7999d34e6d8SKővágó, Zoltán 8009d34e6d8SKővágó, Zoltán if (!PA_CONTEXT_IS_GOOD(state)) { 8019d34e6d8SKővágó, Zoltán qpa_logerr(pa_context_errno(c->context), 8029d34e6d8SKővágó, Zoltán "Wrong context state\n"); 8039d34e6d8SKővágó, Zoltán goto unlock_and_fail; 8049d34e6d8SKővágó, Zoltán } 8059d34e6d8SKővágó, Zoltán 8069d34e6d8SKővágó, Zoltán /* Wait until the context is ready */ 8079d34e6d8SKővágó, Zoltán pa_threaded_mainloop_wait(c->mainloop); 8089d34e6d8SKővágó, Zoltán } 8099d34e6d8SKővágó, Zoltán 8109d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 8119d34e6d8SKővágó, Zoltán return c; 8129d34e6d8SKővágó, Zoltán 8139d34e6d8SKővágó, Zoltán unlock_and_fail: 8149d34e6d8SKővágó, Zoltán pa_threaded_mainloop_unlock(c->mainloop); 8159d34e6d8SKővágó, Zoltán fail: 8169d34e6d8SKővágó, Zoltán AUD_log (AUDIO_CAP, "Failed to initialize PA context"); 8179d34e6d8SKővágó, Zoltán qpa_conn_fini(c); 8189d34e6d8SKővágó, Zoltán return NULL; 8199d34e6d8SKővágó, Zoltán } 8209d34e6d8SKővágó, Zoltán 82171830221SKővágó, Zoltán static void *qpa_audio_init(Audiodev *dev) 822b8e59f18Smalc { 8232c324b28SKővágó, Zoltán paaudio *g; 8242c324b28SKővágó, Zoltán AudiodevPaOptions *popts = &dev->u.pa; 8252c324b28SKővágó, Zoltán const char *server; 8269d34e6d8SKővágó, Zoltán PAConnection *c; 8279d34e6d8SKővágó, Zoltán 8289d34e6d8SKővágó, Zoltán assert(dev->driver == AUDIODEV_DRIVER_PA); 8292c324b28SKővágó, Zoltán 8302c324b28SKővágó, Zoltán if (!popts->has_server) { 831d175505bSGerd Hoffmann char pidfile[64]; 832d175505bSGerd Hoffmann char *runtime; 833d175505bSGerd Hoffmann struct stat st; 834d175505bSGerd Hoffmann 835d175505bSGerd Hoffmann runtime = getenv("XDG_RUNTIME_DIR"); 836d175505bSGerd Hoffmann if (!runtime) { 837d175505bSGerd Hoffmann return NULL; 838d175505bSGerd Hoffmann } 839d175505bSGerd Hoffmann snprintf(pidfile, sizeof(pidfile), "%s/pulse/pid", runtime); 840d175505bSGerd Hoffmann if (stat(pidfile, &st) != 0) { 841d175505bSGerd Hoffmann return NULL; 842d175505bSGerd Hoffmann } 843d175505bSGerd Hoffmann } 844d175505bSGerd Hoffmann 845baea032eSMartin Schrodt if (!qpa_validate_per_direction_opts(dev, popts->in)) { 8469d34e6d8SKővágó, Zoltán return NULL; 847baea032eSMartin Schrodt } 848baea032eSMartin Schrodt if (!qpa_validate_per_direction_opts(dev, popts->out)) { 8499d34e6d8SKővágó, Zoltán return NULL; 850baea032eSMartin Schrodt } 851baea032eSMartin Schrodt 852*b21e2380SMarkus Armbruster g = g_new0(paaudio, 1); 8539d34e6d8SKővágó, Zoltán server = popts->has_server ? popts->server : NULL; 8549d34e6d8SKővágó, Zoltán 8552c324b28SKővágó, Zoltán g->dev = dev; 856ea9ebc2cSMarc-André Lureau 8579d34e6d8SKővágó, Zoltán QTAILQ_FOREACH(c, &pa_conns, list) { 8589d34e6d8SKővágó, Zoltán if (server == NULL || c->server == NULL ? 8599d34e6d8SKővágó, Zoltán server == c->server : 8609d34e6d8SKővágó, Zoltán strcmp(server, c->server) == 0) { 8619d34e6d8SKővágó, Zoltán g->conn = c; 862ea9ebc2cSMarc-André Lureau break; 863ea9ebc2cSMarc-André Lureau } 864ea9ebc2cSMarc-André Lureau } 8659d34e6d8SKővágó, Zoltán if (!g->conn) { 8669d34e6d8SKővágó, Zoltán g->conn = qpa_conn_init(server); 867ea9ebc2cSMarc-André Lureau } 8689d34e6d8SKővágó, Zoltán if (!g->conn) { 8699d34e6d8SKővágó, Zoltán g_free(g); 870ea9ebc2cSMarc-André Lureau return NULL; 871b8e59f18Smalc } 872b8e59f18Smalc 8739d34e6d8SKővágó, Zoltán ++g->conn->refcount; 8749d34e6d8SKővágó, Zoltán return g; 8759d34e6d8SKővágó, Zoltán } 8769d34e6d8SKővágó, Zoltán 8779d34e6d8SKővágó, Zoltán static void qpa_conn_fini(PAConnection *c) 8789d34e6d8SKővágó, Zoltán { 8799d34e6d8SKővágó, Zoltán if (c->mainloop) { 8809d34e6d8SKővágó, Zoltán pa_threaded_mainloop_stop(c->mainloop); 8819d34e6d8SKővágó, Zoltán } 8829d34e6d8SKővágó, Zoltán 8839d34e6d8SKővágó, Zoltán if (c->context) { 8849d34e6d8SKővágó, Zoltán pa_context_disconnect(c->context); 8859d34e6d8SKővágó, Zoltán pa_context_unref(c->context); 8869d34e6d8SKővágó, Zoltán } 8879d34e6d8SKővágó, Zoltán 8889d34e6d8SKővágó, Zoltán if (c->mainloop) { 8899d34e6d8SKővágó, Zoltán pa_threaded_mainloop_free(c->mainloop); 8909d34e6d8SKővágó, Zoltán } 8919d34e6d8SKővágó, Zoltán 8929d34e6d8SKővágó, Zoltán QTAILQ_REMOVE(&pa_conns, c, list); 8939d34e6d8SKővágó, Zoltán g_free(c); 8949d34e6d8SKővágó, Zoltán } 8959d34e6d8SKővágó, Zoltán 896b8e59f18Smalc static void qpa_audio_fini (void *opaque) 897b8e59f18Smalc { 898ea9ebc2cSMarc-André Lureau paaudio *g = opaque; 8999d34e6d8SKővágó, Zoltán PAConnection *c = g->conn; 900ea9ebc2cSMarc-André Lureau 9019d34e6d8SKővágó, Zoltán if (--c->refcount == 0) { 9029d34e6d8SKővágó, Zoltán qpa_conn_fini(c); 903ea9ebc2cSMarc-André Lureau } 904ea9ebc2cSMarc-André Lureau 9059a644c4bSKővágó, Zoltán g_free(g); 906b8e59f18Smalc } 907b8e59f18Smalc 90835f4b58cSblueswir1 static struct audio_pcm_ops qpa_pcm_ops = { 9091dd3e4d1SJuan Quintela .init_out = qpa_init_out, 9101dd3e4d1SJuan Quintela .fini_out = qpa_fini_out, 91149ddd7e1SKővágó, Zoltán .write = qpa_write, 912ddf2050cSVolker Rümelin .buffer_get_free = qpa_buffer_get_free, 913337e8de6SKővágó, Zoltán .get_buffer_out = qpa_get_buffer_out, 914bea29e9fSVolker Rümelin .put_buffer_out = qpa_put_buffer_out, 915571a8c52SKővágó, Zoltán .volume_out = qpa_volume_out, 9161dd3e4d1SJuan Quintela 9171dd3e4d1SJuan Quintela .init_in = qpa_init_in, 9181dd3e4d1SJuan Quintela .fini_in = qpa_fini_in, 91949ddd7e1SKővágó, Zoltán .read = qpa_read, 920337e8de6SKővágó, Zoltán .get_buffer_in = qpa_get_buffer_in, 921337e8de6SKővágó, Zoltán .put_buffer_in = qpa_put_buffer_in, 922571a8c52SKővágó, Zoltán .volume_in = qpa_volume_in 923b8e59f18Smalc }; 924b8e59f18Smalc 925d3893a39SGerd Hoffmann static struct audio_driver pa_audio_driver = { 926bee37f32SJuan Quintela .name = "pa", 927bee37f32SJuan Quintela .descr = "http://www.pulseaudio.org/", 928bee37f32SJuan Quintela .init = qpa_audio_init, 929bee37f32SJuan Quintela .fini = qpa_audio_fini, 930bee37f32SJuan Quintela .pcm_ops = &qpa_pcm_ops, 9311a4ea1e3SMichael S. Tsirkin .can_be_default = 1, 932bee37f32SJuan Quintela .max_voices_out = INT_MAX, 933bee37f32SJuan Quintela .max_voices_in = INT_MAX, 934bee37f32SJuan Quintela .voice_size_out = sizeof (PAVoiceOut), 9356e7a7f3dSMarc-André Lureau .voice_size_in = sizeof (PAVoiceIn), 936b8e59f18Smalc }; 937d3893a39SGerd Hoffmann 938d3893a39SGerd Hoffmann static void register_audio_pa(void) 939d3893a39SGerd Hoffmann { 940d3893a39SGerd Hoffmann audio_driver_register(&pa_audio_driver); 941d3893a39SGerd Hoffmann } 942d3893a39SGerd Hoffmann type_init(register_audio_pa); 943