| /* public domain */ | 
 |  | 
 | #include "qemu/osdep.h" | 
 | #include "qemu/module.h" | 
 | #include "audio.h" | 
 | #include "qapi/error.h" | 
 |  | 
 | #include <pulse/pulseaudio.h> | 
 |  | 
 | #define AUDIO_CAP "pulseaudio" | 
 | #include "audio_int.h" | 
 |  | 
 | typedef struct PAConnection { | 
 |     char *server; | 
 |     int refcount; | 
 |     QTAILQ_ENTRY(PAConnection) list; | 
 |  | 
 |     pa_threaded_mainloop *mainloop; | 
 |     pa_context *context; | 
 | } PAConnection; | 
 |  | 
 | static QTAILQ_HEAD(PAConnectionHead, PAConnection) pa_conns = | 
 |     QTAILQ_HEAD_INITIALIZER(pa_conns); | 
 |  | 
 | typedef struct { | 
 |     Audiodev *dev; | 
 |     PAConnection *conn; | 
 | } paaudio; | 
 |  | 
 | typedef struct { | 
 |     HWVoiceOut hw; | 
 |     pa_stream *stream; | 
 |     paaudio *g; | 
 | } PAVoiceOut; | 
 |  | 
 | typedef struct { | 
 |     HWVoiceIn hw; | 
 |     pa_stream *stream; | 
 |     const void *read_data; | 
 |     size_t read_length; | 
 |     paaudio *g; | 
 | } PAVoiceIn; | 
 |  | 
 | static void qpa_conn_fini(PAConnection *c); | 
 |  | 
 | static void G_GNUC_PRINTF (2, 3) qpa_logerr (int err, const char *fmt, ...) | 
 | { | 
 |     va_list ap; | 
 |  | 
 |     va_start (ap, fmt); | 
 |     AUD_vlog (AUDIO_CAP, fmt, ap); | 
 |     va_end (ap); | 
 |  | 
 |     AUD_log (AUDIO_CAP, "Reason: %s\n", pa_strerror (err)); | 
 | } | 
 |  | 
 | #ifndef PA_CONTEXT_IS_GOOD | 
 | static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) | 
 | { | 
 |     return | 
 |         x == PA_CONTEXT_CONNECTING || | 
 |         x == PA_CONTEXT_AUTHORIZING || | 
 |         x == PA_CONTEXT_SETTING_NAME || | 
 |         x == PA_CONTEXT_READY; | 
 | } | 
 | #endif | 
 |  | 
 | #ifndef PA_STREAM_IS_GOOD | 
 | static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) | 
 | { | 
 |     return | 
 |         x == PA_STREAM_CREATING || | 
 |         x == PA_STREAM_READY; | 
 | } | 
 | #endif | 
 |  | 
 | #define CHECK_SUCCESS_GOTO(c, expression, label, msg)           \ | 
 |     do {                                                        \ | 
 |         if (!(expression)) {                                    \ | 
 |             qpa_logerr(pa_context_errno((c)->context), msg);    \ | 
 |             goto label;                                         \ | 
 |         }                                                       \ | 
 |     } while (0) | 
 |  | 
 | #define CHECK_DEAD_GOTO(c, stream, label, msg)                          \ | 
 |     do {                                                                \ | 
 |         if (!(c)->context || !PA_CONTEXT_IS_GOOD (pa_context_get_state((c)->context)) || \ | 
 |             !(stream) || !PA_STREAM_IS_GOOD (pa_stream_get_state ((stream)))) { \ | 
 |             if (((c)->context && pa_context_get_state ((c)->context) == PA_CONTEXT_FAILED) || \ | 
 |                 ((stream) && pa_stream_get_state ((stream)) == PA_STREAM_FAILED)) { \ | 
 |                 qpa_logerr(pa_context_errno((c)->context), msg);        \ | 
 |             } else {                                                    \ | 
 |                 qpa_logerr(PA_ERR_BADSTATE, msg);                       \ | 
 |             }                                                           \ | 
 |             goto label;                                                 \ | 
 |         }                                                               \ | 
 |     } while (0) | 
 |  | 
 | static void *qpa_get_buffer_in(HWVoiceIn *hw, size_t *size) | 
 | { | 
 |     PAVoiceIn *p = (PAVoiceIn *) hw; | 
 |     PAConnection *c = p->g->conn; | 
 |     int r; | 
 |  | 
 |     pa_threaded_mainloop_lock(c->mainloop); | 
 |  | 
 |     CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, | 
 |                     "pa_threaded_mainloop_lock failed\n"); | 
 |  | 
 |     if (!p->read_length) { | 
 |         r = pa_stream_peek(p->stream, &p->read_data, &p->read_length); | 
 |         CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail, | 
 |                            "pa_stream_peek failed\n"); | 
 |     } | 
 |  | 
 |     *size = MIN(p->read_length, *size); | 
 |  | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 |     return (void *) p->read_data; | 
 |  | 
 | unlock_and_fail: | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 |     *size = 0; | 
 |     return NULL; | 
 | } | 
 |  | 
 | static void qpa_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size) | 
 | { | 
 |     PAVoiceIn *p = (PAVoiceIn *) hw; | 
 |     PAConnection *c = p->g->conn; | 
 |     int r; | 
 |  | 
 |     pa_threaded_mainloop_lock(c->mainloop); | 
 |  | 
 |     CHECK_DEAD_GOTO(c, p->stream, unlock, | 
 |                     "pa_threaded_mainloop_lock failed\n"); | 
 |  | 
 |     assert(buf == p->read_data && size <= p->read_length); | 
 |  | 
 |     p->read_data += size; | 
 |     p->read_length -= size; | 
 |  | 
 |     if (size && !p->read_length) { | 
 |         r = pa_stream_drop(p->stream); | 
 |         CHECK_SUCCESS_GOTO(c, r == 0, unlock, "pa_stream_drop failed\n"); | 
 |     } | 
 |  | 
 | unlock: | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 | } | 
 |  | 
 | static size_t qpa_read(HWVoiceIn *hw, void *data, size_t length) | 
 | { | 
 |     PAVoiceIn *p = (PAVoiceIn *) hw; | 
 |     PAConnection *c = p->g->conn; | 
 |     size_t total = 0; | 
 |  | 
 |     pa_threaded_mainloop_lock(c->mainloop); | 
 |  | 
 |     CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, | 
 |                     "pa_threaded_mainloop_lock failed\n"); | 
 |     if (pa_stream_get_state(p->stream) != PA_STREAM_READY) { | 
 |         /* wait for stream to become ready */ | 
 |         goto unlock; | 
 |     } | 
 |  | 
 |     while (total < length) { | 
 |         size_t l; | 
 |         int r; | 
 |  | 
 |         if (!p->read_length) { | 
 |             r = pa_stream_peek(p->stream, &p->read_data, &p->read_length); | 
 |             CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail, | 
 |                                "pa_stream_peek failed\n"); | 
 |             if (!p->read_length) { | 
 |                 /* buffer is empty */ | 
 |                 break; | 
 |             } | 
 |         } | 
 |  | 
 |         l = MIN(p->read_length, length - total); | 
 |         memcpy((char *)data + total, p->read_data, l); | 
 |  | 
 |         p->read_data += l; | 
 |         p->read_length -= l; | 
 |         total += l; | 
 |  | 
 |         if (!p->read_length) { | 
 |             r = pa_stream_drop(p->stream); | 
 |             CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail, | 
 |                                "pa_stream_drop failed\n"); | 
 |         } | 
 |     } | 
 |  | 
 | unlock: | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 |     return total; | 
 |  | 
 | unlock_and_fail: | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 |     return 0; | 
 | } | 
 |  | 
 | static size_t qpa_buffer_get_free(HWVoiceOut *hw) | 
 | { | 
 |     PAVoiceOut *p = (PAVoiceOut *)hw; | 
 |     PAConnection *c = p->g->conn; | 
 |     size_t l; | 
 |  | 
 |     pa_threaded_mainloop_lock(c->mainloop); | 
 |  | 
 |     CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, | 
 |                     "pa_threaded_mainloop_lock failed\n"); | 
 |     if (pa_stream_get_state(p->stream) != PA_STREAM_READY) { | 
 |         /* wait for stream to become ready */ | 
 |         l = 0; | 
 |         goto unlock; | 
 |     } | 
 |  | 
 |     l = pa_stream_writable_size(p->stream); | 
 |     CHECK_SUCCESS_GOTO(c, l != (size_t) -1, unlock_and_fail, | 
 |                        "pa_stream_writable_size failed\n"); | 
 |  | 
 | unlock: | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 |     return l; | 
 |  | 
 | unlock_and_fail: | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 |     return 0; | 
 | } | 
 |  | 
 | static void *qpa_get_buffer_out(HWVoiceOut *hw, size_t *size) | 
 | { | 
 |     PAVoiceOut *p = (PAVoiceOut *)hw; | 
 |     PAConnection *c = p->g->conn; | 
 |     void *ret; | 
 |     int r; | 
 |  | 
 |     pa_threaded_mainloop_lock(c->mainloop); | 
 |  | 
 |     CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, | 
 |                     "pa_threaded_mainloop_lock failed\n"); | 
 |  | 
 |     *size = -1; | 
 |     r = pa_stream_begin_write(p->stream, &ret, size); | 
 |     CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail, | 
 |                        "pa_stream_begin_write failed\n"); | 
 |  | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 |     return ret; | 
 |  | 
 | unlock_and_fail: | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 |     *size = 0; | 
 |     return NULL; | 
 | } | 
 |  | 
 | static size_t qpa_put_buffer_out(HWVoiceOut *hw, void *data, size_t length) | 
 | { | 
 |     PAVoiceOut *p = (PAVoiceOut *)hw; | 
 |     PAConnection *c = p->g->conn; | 
 |     int r; | 
 |  | 
 |     pa_threaded_mainloop_lock(c->mainloop); | 
 |  | 
 |     CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, | 
 |                     "pa_threaded_mainloop_lock failed\n"); | 
 |  | 
 |     r = pa_stream_write(p->stream, data, length, NULL, 0LL, PA_SEEK_RELATIVE); | 
 |     CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail, "pa_stream_write failed\n"); | 
 |  | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 |     return length; | 
 |  | 
 | unlock_and_fail: | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 |     return 0; | 
 | } | 
 |  | 
 | static size_t qpa_write(HWVoiceOut *hw, void *data, size_t length) | 
 | { | 
 |     PAVoiceOut *p = (PAVoiceOut *) hw; | 
 |     PAConnection *c = p->g->conn; | 
 |     size_t l; | 
 |     int r; | 
 |  | 
 |     pa_threaded_mainloop_lock(c->mainloop); | 
 |  | 
 |     CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, | 
 |                     "pa_threaded_mainloop_lock failed\n"); | 
 |     if (pa_stream_get_state(p->stream) != PA_STREAM_READY) { | 
 |         /* wait for stream to become ready */ | 
 |         l = 0; | 
 |         goto unlock; | 
 |     } | 
 |  | 
 |     l = pa_stream_writable_size(p->stream); | 
 |  | 
 |     CHECK_SUCCESS_GOTO(c, l != (size_t) -1, unlock_and_fail, | 
 |                        "pa_stream_writable_size failed\n"); | 
 |  | 
 |     if (l > length) { | 
 |         l = length; | 
 |     } | 
 |  | 
 |     r = pa_stream_write(p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE); | 
 |     CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail, "pa_stream_write failed\n"); | 
 |  | 
 | unlock: | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 |     return l; | 
 |  | 
 | unlock_and_fail: | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 |     return 0; | 
 | } | 
 |  | 
 | static pa_sample_format_t audfmt_to_pa (AudioFormat afmt, int endianness) | 
 | { | 
 |     int format; | 
 |  | 
 |     switch (afmt) { | 
 |     case AUDIO_FORMAT_S8: | 
 |     case AUDIO_FORMAT_U8: | 
 |         format = PA_SAMPLE_U8; | 
 |         break; | 
 |     case AUDIO_FORMAT_S16: | 
 |     case AUDIO_FORMAT_U16: | 
 |         format = endianness ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE; | 
 |         break; | 
 |     case AUDIO_FORMAT_S32: | 
 |     case AUDIO_FORMAT_U32: | 
 |         format = endianness ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE; | 
 |         break; | 
 |     case AUDIO_FORMAT_F32: | 
 |         format = endianness ? PA_SAMPLE_FLOAT32BE : PA_SAMPLE_FLOAT32LE; | 
 |         break; | 
 |     default: | 
 |         dolog ("Internal logic error: Bad audio format %d\n", afmt); | 
 |         format = PA_SAMPLE_U8; | 
 |         break; | 
 |     } | 
 |     return format; | 
 | } | 
 |  | 
 | static AudioFormat pa_to_audfmt (pa_sample_format_t fmt, int *endianness) | 
 | { | 
 |     switch (fmt) { | 
 |     case PA_SAMPLE_U8: | 
 |         return AUDIO_FORMAT_U8; | 
 |     case PA_SAMPLE_S16BE: | 
 |         *endianness = 1; | 
 |         return AUDIO_FORMAT_S16; | 
 |     case PA_SAMPLE_S16LE: | 
 |         *endianness = 0; | 
 |         return AUDIO_FORMAT_S16; | 
 |     case PA_SAMPLE_S32BE: | 
 |         *endianness = 1; | 
 |         return AUDIO_FORMAT_S32; | 
 |     case PA_SAMPLE_S32LE: | 
 |         *endianness = 0; | 
 |         return AUDIO_FORMAT_S32; | 
 |     case PA_SAMPLE_FLOAT32BE: | 
 |         *endianness = 1; | 
 |         return AUDIO_FORMAT_F32; | 
 |     case PA_SAMPLE_FLOAT32LE: | 
 |         *endianness = 0; | 
 |         return AUDIO_FORMAT_F32; | 
 |     default: | 
 |         dolog ("Internal logic error: Bad pa_sample_format %d\n", fmt); | 
 |         return AUDIO_FORMAT_U8; | 
 |     } | 
 | } | 
 |  | 
 | static void context_state_cb (pa_context *c, void *userdata) | 
 | { | 
 |     PAConnection *conn = userdata; | 
 |  | 
 |     switch (pa_context_get_state(c)) { | 
 |     case PA_CONTEXT_READY: | 
 |     case PA_CONTEXT_TERMINATED: | 
 |     case PA_CONTEXT_FAILED: | 
 |         pa_threaded_mainloop_signal(conn->mainloop, 0); | 
 |         break; | 
 |  | 
 |     case PA_CONTEXT_UNCONNECTED: | 
 |     case PA_CONTEXT_CONNECTING: | 
 |     case PA_CONTEXT_AUTHORIZING: | 
 |     case PA_CONTEXT_SETTING_NAME: | 
 |         break; | 
 |     } | 
 | } | 
 |  | 
 | static void stream_state_cb (pa_stream *s, void * userdata) | 
 | { | 
 |     PAConnection *c = userdata; | 
 |  | 
 |     switch (pa_stream_get_state (s)) { | 
 |  | 
 |     case PA_STREAM_READY: | 
 |     case PA_STREAM_FAILED: | 
 |     case PA_STREAM_TERMINATED: | 
 |         pa_threaded_mainloop_signal(c->mainloop, 0); | 
 |         break; | 
 |  | 
 |     case PA_STREAM_UNCONNECTED: | 
 |     case PA_STREAM_CREATING: | 
 |         break; | 
 |     } | 
 | } | 
 |  | 
 | static pa_stream *qpa_simple_new ( | 
 |         PAConnection *c, | 
 |         const char *name, | 
 |         pa_stream_direction_t dir, | 
 |         const char *dev, | 
 |         const pa_sample_spec *ss, | 
 |         const pa_buffer_attr *attr, | 
 |         int *rerror) | 
 | { | 
 |     int r; | 
 |     pa_stream *stream = NULL; | 
 |     pa_stream_flags_t flags; | 
 |     pa_channel_map map; | 
 |  | 
 |     pa_threaded_mainloop_lock(c->mainloop); | 
 |  | 
 |     pa_channel_map_init(&map); | 
 |     map.channels = ss->channels; | 
 |  | 
 |     /* | 
 |      * TODO: This currently expects the only frontend supporting more than 2 | 
 |      * channels is the usb-audio.  We will need some means to set channel | 
 |      * order when a new frontend gains multi-channel support. | 
 |      */ | 
 |     switch (ss->channels) { | 
 |     case 1: | 
 |         map.map[0] = PA_CHANNEL_POSITION_MONO; | 
 |         break; | 
 |  | 
 |     case 2: | 
 |         map.map[0] = PA_CHANNEL_POSITION_LEFT; | 
 |         map.map[1] = PA_CHANNEL_POSITION_RIGHT; | 
 |         break; | 
 |  | 
 |     case 6: | 
 |         map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; | 
 |         map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; | 
 |         map.map[2] = PA_CHANNEL_POSITION_CENTER; | 
 |         map.map[3] = PA_CHANNEL_POSITION_LFE; | 
 |         map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT; | 
 |         map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT; | 
 |         break; | 
 |  | 
 |     case 8: | 
 |         map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; | 
 |         map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; | 
 |         map.map[2] = PA_CHANNEL_POSITION_CENTER; | 
 |         map.map[3] = PA_CHANNEL_POSITION_LFE; | 
 |         map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT; | 
 |         map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT; | 
 |         map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; | 
 |         map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; | 
 |         break; | 
 |  | 
 |     default: | 
 |         dolog("Internal error: unsupported channel count %d\n", ss->channels); | 
 |         goto fail; | 
 |     } | 
 |  | 
 |     stream = pa_stream_new(c->context, name, ss, &map); | 
 |     if (!stream) { | 
 |         goto fail; | 
 |     } | 
 |  | 
 |     pa_stream_set_state_callback(stream, stream_state_cb, c); | 
 |  | 
 |     flags = PA_STREAM_EARLY_REQUESTS; | 
 |  | 
 |     if (dev) { | 
 |         /* don't move the stream if the user specified a sink/source */ | 
 |         flags |= PA_STREAM_DONT_MOVE; | 
 |     } | 
 |  | 
 |     if (dir == PA_STREAM_PLAYBACK) { | 
 |         r = pa_stream_connect_playback(stream, dev, attr, flags, NULL, NULL); | 
 |     } else { | 
 |         r = pa_stream_connect_record(stream, dev, attr, flags); | 
 |     } | 
 |  | 
 |     if (r < 0) { | 
 |         goto fail; | 
 |     } | 
 |  | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 |  | 
 |     return stream; | 
 |  | 
 | fail: | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 |  | 
 |     if (stream) { | 
 |         pa_stream_unref (stream); | 
 |     } | 
 |  | 
 |     *rerror = pa_context_errno(c->context); | 
 |  | 
 |     return NULL; | 
 | } | 
 |  | 
 | static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, | 
 |                         void *drv_opaque) | 
 | { | 
 |     int error; | 
 |     pa_sample_spec ss; | 
 |     pa_buffer_attr ba; | 
 |     struct audsettings obt_as = *as; | 
 |     PAVoiceOut *pa = (PAVoiceOut *) hw; | 
 |     paaudio *g = pa->g = drv_opaque; | 
 |     AudiodevPaOptions *popts = &g->dev->u.pa; | 
 |     AudiodevPaPerDirectionOptions *ppdo = popts->out; | 
 |     PAConnection *c = g->conn; | 
 |  | 
 |     ss.format = audfmt_to_pa (as->fmt, as->endianness); | 
 |     ss.channels = as->nchannels; | 
 |     ss.rate = as->freq; | 
 |  | 
 |     ba.tlength = pa_usec_to_bytes(ppdo->latency, &ss); | 
 |     ba.minreq = pa_usec_to_bytes(MIN(ppdo->latency >> 2, | 
 |                                      (g->dev->timer_period >> 2) * 3), &ss); | 
 |     ba.maxlength = -1; | 
 |     ba.prebuf = -1; | 
 |  | 
 |     obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); | 
 |  | 
 |     pa->stream = qpa_simple_new ( | 
 |         c, | 
 |         ppdo->stream_name ?: g->dev->id, | 
 |         PA_STREAM_PLAYBACK, | 
 |         ppdo->name, | 
 |         &ss, | 
 |         &ba,                    /* buffering attributes */ | 
 |         &error | 
 |         ); | 
 |     if (!pa->stream) { | 
 |         qpa_logerr (error, "pa_simple_new for playback failed\n"); | 
 |         goto fail1; | 
 |     } | 
 |  | 
 |     audio_pcm_init_info (&hw->info, &obt_as); | 
 |     /* hw->samples counts in frames */ | 
 |     hw->samples = audio_buffer_frames( | 
 |         qapi_AudiodevPaPerDirectionOptions_base(ppdo), &obt_as, 46440); | 
 |  | 
 |     return 0; | 
 |  | 
 |  fail1: | 
 |     return -1; | 
 | } | 
 |  | 
 | static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) | 
 | { | 
 |     int error; | 
 |     pa_sample_spec ss; | 
 |     pa_buffer_attr ba; | 
 |     struct audsettings obt_as = *as; | 
 |     PAVoiceIn *pa = (PAVoiceIn *) hw; | 
 |     paaudio *g = pa->g = drv_opaque; | 
 |     AudiodevPaOptions *popts = &g->dev->u.pa; | 
 |     AudiodevPaPerDirectionOptions *ppdo = popts->in; | 
 |     PAConnection *c = g->conn; | 
 |  | 
 |     ss.format = audfmt_to_pa (as->fmt, as->endianness); | 
 |     ss.channels = as->nchannels; | 
 |     ss.rate = as->freq; | 
 |  | 
 |     ba.fragsize = pa_usec_to_bytes((g->dev->timer_period >> 1) * 3, &ss); | 
 |     ba.maxlength = pa_usec_to_bytes( | 
 |         MAX(ppdo->latency, g->dev->timer_period * 3), &ss); | 
 |     ba.minreq = -1; | 
 |     ba.prebuf = -1; | 
 |  | 
 |     obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); | 
 |  | 
 |     pa->stream = qpa_simple_new ( | 
 |         c, | 
 |         ppdo->stream_name ?: g->dev->id, | 
 |         PA_STREAM_RECORD, | 
 |         ppdo->name, | 
 |         &ss, | 
 |         &ba,                    /* buffering attributes */ | 
 |         &error | 
 |         ); | 
 |     if (!pa->stream) { | 
 |         qpa_logerr (error, "pa_simple_new for capture failed\n"); | 
 |         goto fail1; | 
 |     } | 
 |  | 
 |     audio_pcm_init_info (&hw->info, &obt_as); | 
 |     /* hw->samples counts in frames */ | 
 |     hw->samples = audio_buffer_frames( | 
 |         qapi_AudiodevPaPerDirectionOptions_base(ppdo), &obt_as, 46440); | 
 |  | 
 |     return 0; | 
 |  | 
 |  fail1: | 
 |     return -1; | 
 | } | 
 |  | 
 | static void qpa_simple_disconnect(PAConnection *c, pa_stream *stream) | 
 | { | 
 |     int err; | 
 |  | 
 |     /* | 
 |      * wait until actually connects. workaround pa bug #247 | 
 |      * https://gitlab.freedesktop.org/pulseaudio/pulseaudio/issues/247 | 
 |      */ | 
 |     while (pa_stream_get_state(stream) == PA_STREAM_CREATING) { | 
 |         pa_threaded_mainloop_wait(c->mainloop); | 
 |     } | 
 |  | 
 |     err = pa_stream_disconnect(stream); | 
 |     if (err != 0) { | 
 |         dolog("Failed to disconnect! err=%d\n", err); | 
 |     } | 
 |     pa_stream_unref(stream); | 
 | } | 
 |  | 
 | static void qpa_fini_out (HWVoiceOut *hw) | 
 | { | 
 |     PAVoiceOut *pa = (PAVoiceOut *) hw; | 
 |  | 
 |     if (pa->stream) { | 
 |         PAConnection *c = pa->g->conn; | 
 |  | 
 |         pa_threaded_mainloop_lock(c->mainloop); | 
 |         qpa_simple_disconnect(c, pa->stream); | 
 |         pa->stream = NULL; | 
 |         pa_threaded_mainloop_unlock(c->mainloop); | 
 |     } | 
 | } | 
 |  | 
 | static void qpa_fini_in (HWVoiceIn *hw) | 
 | { | 
 |     PAVoiceIn *pa = (PAVoiceIn *) hw; | 
 |  | 
 |     if (pa->stream) { | 
 |         PAConnection *c = pa->g->conn; | 
 |  | 
 |         pa_threaded_mainloop_lock(c->mainloop); | 
 |         if (pa->read_length) { | 
 |             int r = pa_stream_drop(pa->stream); | 
 |             if (r) { | 
 |                 qpa_logerr(pa_context_errno(c->context), | 
 |                            "pa_stream_drop failed\n"); | 
 |             } | 
 |             pa->read_length = 0; | 
 |         } | 
 |         qpa_simple_disconnect(c, pa->stream); | 
 |         pa->stream = NULL; | 
 |         pa_threaded_mainloop_unlock(c->mainloop); | 
 |     } | 
 | } | 
 |  | 
 | static void qpa_volume_out(HWVoiceOut *hw, Volume *vol) | 
 | { | 
 |     PAVoiceOut *pa = (PAVoiceOut *) hw; | 
 |     pa_operation *op; | 
 |     pa_cvolume v; | 
 |     PAConnection *c = pa->g->conn; | 
 |     int i; | 
 |  | 
 | #ifdef PA_CHECK_VERSION    /* macro is present in 0.9.16+ */ | 
 |     pa_cvolume_init (&v);  /* function is present in 0.9.13+ */ | 
 | #endif | 
 |  | 
 |     v.channels = vol->channels; | 
 |     for (i = 0; i < vol->channels; ++i) { | 
 |         v.values[i] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->vol[i]) / 255; | 
 |     } | 
 |  | 
 |     pa_threaded_mainloop_lock(c->mainloop); | 
 |  | 
 |     op = pa_context_set_sink_input_volume(c->context, | 
 |                                           pa_stream_get_index(pa->stream), | 
 |                                           &v, NULL, NULL); | 
 |     if (!op) { | 
 |         qpa_logerr(pa_context_errno(c->context), | 
 |                    "set_sink_input_volume() failed\n"); | 
 |     } else { | 
 |         pa_operation_unref(op); | 
 |     } | 
 |  | 
 |     op = pa_context_set_sink_input_mute(c->context, | 
 |                                         pa_stream_get_index(pa->stream), | 
 |                                         vol->mute, NULL, NULL); | 
 |     if (!op) { | 
 |         qpa_logerr(pa_context_errno(c->context), | 
 |                    "set_sink_input_mute() failed\n"); | 
 |     } else { | 
 |         pa_operation_unref(op); | 
 |     } | 
 |  | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 | } | 
 |  | 
 | static void qpa_volume_in(HWVoiceIn *hw, Volume *vol) | 
 | { | 
 |     PAVoiceIn *pa = (PAVoiceIn *) hw; | 
 |     pa_operation *op; | 
 |     pa_cvolume v; | 
 |     PAConnection *c = pa->g->conn; | 
 |     int i; | 
 |  | 
 | #ifdef PA_CHECK_VERSION | 
 |     pa_cvolume_init (&v); | 
 | #endif | 
 |  | 
 |     v.channels = vol->channels; | 
 |     for (i = 0; i < vol->channels; ++i) { | 
 |         v.values[i] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->vol[i]) / 255; | 
 |     } | 
 |  | 
 |     pa_threaded_mainloop_lock(c->mainloop); | 
 |  | 
 |     op = pa_context_set_source_output_volume(c->context, | 
 |         pa_stream_get_index(pa->stream), | 
 |         &v, NULL, NULL); | 
 |     if (!op) { | 
 |         qpa_logerr(pa_context_errno(c->context), | 
 |                    "set_source_output_volume() failed\n"); | 
 |     } else { | 
 |         pa_operation_unref(op); | 
 |     } | 
 |  | 
 |     op = pa_context_set_source_output_mute(c->context, | 
 |         pa_stream_get_index(pa->stream), | 
 |         vol->mute, NULL, NULL); | 
 |     if (!op) { | 
 |         qpa_logerr(pa_context_errno(c->context), | 
 |                    "set_source_output_mute() failed\n"); | 
 |     } else { | 
 |         pa_operation_unref(op); | 
 |     } | 
 |  | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 | } | 
 |  | 
 | static int qpa_validate_per_direction_opts(Audiodev *dev, | 
 |                                            AudiodevPaPerDirectionOptions *pdo) | 
 | { | 
 |     if (!pdo->has_latency) { | 
 |         pdo->has_latency = true; | 
 |         pdo->latency = 46440; | 
 |     } | 
 |     return 1; | 
 | } | 
 |  | 
 | /* common */ | 
 | static void *qpa_conn_init(const char *server) | 
 | { | 
 |     PAConnection *c = g_new0(PAConnection, 1); | 
 |     QTAILQ_INSERT_TAIL(&pa_conns, c, list); | 
 |  | 
 |     c->mainloop = pa_threaded_mainloop_new(); | 
 |     if (!c->mainloop) { | 
 |         goto fail; | 
 |     } | 
 |  | 
 |     c->context = pa_context_new(pa_threaded_mainloop_get_api(c->mainloop), | 
 |                                 audio_application_name()); | 
 |     if (!c->context) { | 
 |         goto fail; | 
 |     } | 
 |  | 
 |     pa_context_set_state_callback(c->context, context_state_cb, c); | 
 |  | 
 |     if (pa_context_connect(c->context, server, 0, NULL) < 0) { | 
 |         qpa_logerr(pa_context_errno(c->context), | 
 |                    "pa_context_connect() failed\n"); | 
 |         goto fail; | 
 |     } | 
 |  | 
 |     pa_threaded_mainloop_lock(c->mainloop); | 
 |  | 
 |     if (pa_threaded_mainloop_start(c->mainloop) < 0) { | 
 |         goto unlock_and_fail; | 
 |     } | 
 |  | 
 |     for (;;) { | 
 |         pa_context_state_t state; | 
 |  | 
 |         state = pa_context_get_state(c->context); | 
 |  | 
 |         if (state == PA_CONTEXT_READY) { | 
 |             break; | 
 |         } | 
 |  | 
 |         if (!PA_CONTEXT_IS_GOOD(state)) { | 
 |             qpa_logerr(pa_context_errno(c->context), | 
 |                        "Wrong context state\n"); | 
 |             goto unlock_and_fail; | 
 |         } | 
 |  | 
 |         /* Wait until the context is ready */ | 
 |         pa_threaded_mainloop_wait(c->mainloop); | 
 |     } | 
 |  | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 |     return c; | 
 |  | 
 | unlock_and_fail: | 
 |     pa_threaded_mainloop_unlock(c->mainloop); | 
 | fail: | 
 |     AUD_log (AUDIO_CAP, "Failed to initialize PA context"); | 
 |     qpa_conn_fini(c); | 
 |     return NULL; | 
 | } | 
 |  | 
 | static void *qpa_audio_init(Audiodev *dev, Error **errp) | 
 | { | 
 |     paaudio *g; | 
 |     AudiodevPaOptions *popts = &dev->u.pa; | 
 |     const char *server; | 
 |     PAConnection *c; | 
 |  | 
 |     assert(dev->driver == AUDIODEV_DRIVER_PA); | 
 |  | 
 |     if (!popts->server) { | 
 |         char pidfile[64]; | 
 |         char *runtime; | 
 |         struct stat st; | 
 |  | 
 |         runtime = getenv("XDG_RUNTIME_DIR"); | 
 |         if (!runtime) { | 
 |             error_setg(errp, "XDG_RUNTIME_DIR not set"); | 
 |             return NULL; | 
 |         } | 
 |         snprintf(pidfile, sizeof(pidfile), "%s/pulse/pid", runtime); | 
 |         if (stat(pidfile, &st) != 0) { | 
 |             error_setg_errno(errp, errno, "could not stat pidfile %s", pidfile); | 
 |             return NULL; | 
 |         } | 
 |     } | 
 |  | 
 |     if (!qpa_validate_per_direction_opts(dev, popts->in)) { | 
 |         return NULL; | 
 |     } | 
 |     if (!qpa_validate_per_direction_opts(dev, popts->out)) { | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     g = g_new0(paaudio, 1); | 
 |     server = popts->server; | 
 |  | 
 |     g->dev = dev; | 
 |  | 
 |     QTAILQ_FOREACH(c, &pa_conns, list) { | 
 |         if (server == NULL || c->server == NULL ? | 
 |             server == c->server : | 
 |             strcmp(server, c->server) == 0) { | 
 |             g->conn = c; | 
 |             break; | 
 |         } | 
 |     } | 
 |     if (!g->conn) { | 
 |         g->conn = qpa_conn_init(server); | 
 |     } | 
 |     if (!g->conn) { | 
 |         g_free(g); | 
 |         error_setg(errp, "could not connect to PulseAudio server"); | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     ++g->conn->refcount; | 
 |     return g; | 
 | } | 
 |  | 
 | static void qpa_conn_fini(PAConnection *c) | 
 | { | 
 |     if (c->mainloop) { | 
 |         pa_threaded_mainloop_stop(c->mainloop); | 
 |     } | 
 |  | 
 |     if (c->context) { | 
 |         pa_context_disconnect(c->context); | 
 |         pa_context_unref(c->context); | 
 |     } | 
 |  | 
 |     if (c->mainloop) { | 
 |         pa_threaded_mainloop_free(c->mainloop); | 
 |     } | 
 |  | 
 |     QTAILQ_REMOVE(&pa_conns, c, list); | 
 |     g_free(c); | 
 | } | 
 |  | 
 | static void qpa_audio_fini (void *opaque) | 
 | { | 
 |     paaudio *g = opaque; | 
 |     PAConnection *c = g->conn; | 
 |  | 
 |     if (--c->refcount == 0) { | 
 |         qpa_conn_fini(c); | 
 |     } | 
 |  | 
 |     g_free(g); | 
 | } | 
 |  | 
 | static struct audio_pcm_ops qpa_pcm_ops = { | 
 |     .init_out = qpa_init_out, | 
 |     .fini_out = qpa_fini_out, | 
 |     .write    = qpa_write, | 
 |     .buffer_get_free = qpa_buffer_get_free, | 
 |     .get_buffer_out = qpa_get_buffer_out, | 
 |     .put_buffer_out = qpa_put_buffer_out, | 
 |     .volume_out = qpa_volume_out, | 
 |  | 
 |     .init_in  = qpa_init_in, | 
 |     .fini_in  = qpa_fini_in, | 
 |     .read     = qpa_read, | 
 |     .get_buffer_in = qpa_get_buffer_in, | 
 |     .put_buffer_in = qpa_put_buffer_in, | 
 |     .volume_in = qpa_volume_in | 
 | }; | 
 |  | 
 | static struct audio_driver pa_audio_driver = { | 
 |     .name           = "pa", | 
 |     .descr          = "http://www.pulseaudio.org/", | 
 |     .init           = qpa_audio_init, | 
 |     .fini           = qpa_audio_fini, | 
 |     .pcm_ops        = &qpa_pcm_ops, | 
 |     .max_voices_out = INT_MAX, | 
 |     .max_voices_in  = INT_MAX, | 
 |     .voice_size_out = sizeof (PAVoiceOut), | 
 |     .voice_size_in  = sizeof (PAVoiceIn), | 
 | }; | 
 |  | 
 | static void register_audio_pa(void) | 
 | { | 
 |     audio_driver_register(&pa_audio_driver); | 
 | } | 
 | type_init(register_audio_pa); |