diff --git a/audio/audio.c b/audio/audio.c
index beafed209b..6f42a019b0 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -2066,3 +2066,8 @@ void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol)
         }
     }
 }
+
+int64_t audio_get_timer_ticks(void)
+{
+    return conf.period.ticks;
+}
diff --git a/audio/audio_int.h b/audio/audio_int.h
index 5bcb1c60e1..2f7fc4f8ac 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -214,6 +214,8 @@ extern struct audio_driver pa_audio_driver;
 extern struct audio_driver spice_audio_driver;
 extern const struct mixeng_volume nominal_volume;
 
+int64_t audio_get_timer_ticks(void);
+
 void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as);
 void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len);
 
diff --git a/audio/paaudio.c b/audio/paaudio.c
index 65beb6f010..e76e1e006f 100644
--- a/audio/paaudio.c
+++ b/audio/paaudio.c
@@ -1,16 +1,22 @@
 /* public domain */
 #include "qemu/osdep.h"
-#include "qemu-common.h"
+#include "qemu/timer.h"
 #include "audio.h"
 
 #include <pulse/pulseaudio.h>
 
 #define AUDIO_CAP "pulseaudio"
+#define DEBUG
 #include "audio_int.h"
-#include "audio_pt_int.h"
 
 typedef struct {
-    int samples;
+    int buffer_size_out;
+    int buffer_size_in;
+    int tlength;
+    int fragsize;
+    int maxlength_in;
+    int adjust_latency_out;
+    int adjust_latency_in;
     char *server;
     char *sink;
     char *source;
@@ -24,28 +30,18 @@ typedef struct {
 
 typedef struct {
     HWVoiceOut hw;
-    int done;
-    int live;
-    int decr;
-    int rpos;
     pa_stream *stream;
-    void *pcm_buf;
-    struct audio_pt pt;
     paaudio *g;
+    pa_sample_spec ss;
+    pa_buffer_attr ba;
 } PAVoiceOut;
 
 typedef struct {
     HWVoiceIn hw;
-    int done;
-    int dead;
-    int incr;
-    int wpos;
     pa_stream *stream;
-    void *pcm_buf;
-    struct audio_pt pt;
-    const void *read_data;
-    size_t read_index, read_length;
     paaudio *g;
+    pa_sample_spec ss;
+    pa_buffer_attr ba;
 } PAVoiceIn;
 
 static void qpa_audio_fini(void *opaque);
@@ -109,182 +105,59 @@ static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x)
         }                                                               \
     } while (0);
 
-static int qpa_simple_read (PAVoiceIn *p, void *data, size_t length, int *rerror)
-{
-    paaudio *g = p->g;
-
-    pa_threaded_mainloop_lock (g->mainloop);
-
-    CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail);
-
-    while (length > 0) {
-        size_t l;
-
-        while (!p->read_data) {
-            int r;
-
-            r = pa_stream_peek (p->stream, &p->read_data, &p->read_length);
-            CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail);
-
-            if (!p->read_data) {
-                pa_threaded_mainloop_wait (g->mainloop);
-                CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail);
-            } else {
-                p->read_index = 0;
-            }
-        }
-
-        l = p->read_length < length ? p->read_length : length;
-        memcpy (data, (const uint8_t *) p->read_data+p->read_index, l);
-
-        data = (uint8_t *) data + l;
-        length -= l;
-
-        p->read_index += l;
-        p->read_length -= l;
-
-        if (!p->read_length) {
-            int r;
-
-            r = pa_stream_drop (p->stream);
-            p->read_data = NULL;
-            p->read_length = 0;
-            p->read_index = 0;
-
-            CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail);
-        }
-    }
-
-    pa_threaded_mainloop_unlock (g->mainloop);
-    return 0;
-
-unlock_and_fail:
-    pa_threaded_mainloop_unlock (g->mainloop);
-    return -1;
-}
-
-static int qpa_simple_write (PAVoiceOut *p, const void *data, size_t length, int *rerror)
+static int qpa_run_out(HWVoiceOut *hw, int live)
 {
-    paaudio *g = p->g;
-
-    pa_threaded_mainloop_lock (g->mainloop);
-
-    CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail);
-
-    while (length > 0) {
-        size_t l;
-        int r;
-
-        while (!(l = pa_stream_writable_size (p->stream))) {
-            pa_threaded_mainloop_wait (g->mainloop);
-            CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail);
-        }
-
-        CHECK_SUCCESS_GOTO (g, rerror, l != (size_t) -1, unlock_and_fail);
-
-        if (l > length) {
-            l = length;
-        }
-
-        r = pa_stream_write (p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE);
-        CHECK_SUCCESS_GOTO (g, rerror, r >= 0, unlock_and_fail);
-
-        data = (const uint8_t *) data + l;
-        length -= l;
-    }
-
-    pa_threaded_mainloop_unlock (g->mainloop);
-    return 0;
-
-unlock_and_fail:
-    pa_threaded_mainloop_unlock (g->mainloop);
-    return -1;
-}
-
-static void *qpa_thread_out (void *arg)
-{
-    PAVoiceOut *pa = arg;
-    HWVoiceOut *hw = &pa->hw;
-
-    if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) {
-        return NULL;
-    }
+    PAVoiceOut *pa = (PAVoiceOut *) hw;
+    int rpos, decr, samples;
+    size_t avail_bytes, max_bytes;
+    struct st_sample *src;
+    void *pa_dst;
+    int error = 0;
+    int *rerror = &error;
+    int r;
 
-    for (;;) {
-        int decr, to_mix, rpos;
+    decr = 0;
+    rpos = hw->rpos;
 
-        for (;;) {
-            if (pa->done) {
-                goto exit;
-            }
+    pa_threaded_mainloop_lock(pa->g->mainloop);
+    CHECK_DEAD_GOTO(pa->g, pa->stream, rerror, fail);
 
-            if (pa->live > 0) {
-                break;
-            }
+    avail_bytes = (size_t) live << hw->info.shift;
 
-            if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) {
-                goto exit;
-            }
-        }
+    max_bytes = pa_stream_writable_size(pa->stream);
+    CHECK_SUCCESS_GOTO(pa->g, rerror, max_bytes != -1, fail);
 
-        decr = to_mix = audio_MIN (pa->live, pa->g->conf.samples >> 2);
-        rpos = pa->rpos;
+    samples = (int)(audio_MIN(avail_bytes, max_bytes)) >> hw->info.shift;
+    while (samples) {
+        int convert_samples = audio_MIN(samples, hw->samples - rpos);
+        size_t b_wanted = (size_t) convert_samples << hw->info.shift;
+        size_t b_effective = b_wanted;
 
-        if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) {
-            return NULL;
-        }
+        r = pa_stream_begin_write(pa->stream, &pa_dst, &b_effective);
+        CHECK_SUCCESS_GOTO(pa->g, rerror, r == 0, fail);
+        CHECK_SUCCESS_GOTO(pa->g, (int *)0, b_effective == b_wanted, fail);
 
-        while (to_mix) {
-            int error;
-            int chunk = audio_MIN (to_mix, hw->samples - rpos);
-            struct st_sample *src = hw->mix_buf + rpos;
+        src = hw->mix_buf + rpos;
+        hw->clip(pa_dst, src, convert_samples);
 
-            hw->clip (pa->pcm_buf, src, chunk);
-
-            if (qpa_simple_write (pa, pa->pcm_buf,
-                                  chunk << hw->info.shift, &error) < 0) {
-                qpa_logerr (error, "pa_simple_write failed\n");
-                return NULL;
-            }
+        r = pa_stream_write(pa->stream, pa_dst, b_effective,
+                            NULL, 0LL, PA_SEEK_RELATIVE);
+        CHECK_SUCCESS_GOTO(pa->g, rerror, r >= 0, fail);
 
-            rpos = (rpos + chunk) % hw->samples;
-            to_mix -= chunk;
-        }
-
-        if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) {
-            return NULL;
-        }
-
-        pa->rpos = rpos;
-        pa->live -= decr;
-        pa->decr += decr;
+        rpos = (rpos + convert_samples) % hw->samples;
+        samples -= convert_samples;
+        decr += convert_samples;
     }
 
- exit:
-    audio_pt_unlock (&pa->pt, AUDIO_FUNC);
-    return NULL;
-}
-
-static int qpa_run_out (HWVoiceOut *hw, int live)
-{
-    int decr;
-    PAVoiceOut *pa = (PAVoiceOut *) hw;
-
-    if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) {
-        return 0;
-    }
+    bail:
+    pa_threaded_mainloop_unlock(pa->g->mainloop);
 
-    decr = audio_MIN (live, pa->decr);
-    pa->decr -= decr;
-    pa->live = live - decr;
-    hw->rpos = pa->rpos;
-    if (pa->live > 0) {
-        audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC);
-    }
-    else {
-        audio_pt_unlock (&pa->pt, AUDIO_FUNC);
-    }
+    hw->rpos = rpos;
     return decr;
+
+fail:
+    qpa_logerr(error, "qpa_run_out failed\n");
+    goto bail;
 }
 
 static int qpa_write (SWVoiceOut *sw, void *buf, int len)
@@ -292,92 +165,68 @@ static int qpa_write (SWVoiceOut *sw, void *buf, int len)
     return audio_pcm_sw_write (sw, buf, len);
 }
 
-/* capture */
-static void *qpa_thread_in (void *arg)
+static int qpa_run_in(HWVoiceIn *hw)
 {
-    PAVoiceIn *pa = arg;
-    HWVoiceIn *hw = &pa->hw;
+    PAVoiceIn *pa = (PAVoiceIn *) hw;
+    int wpos, incr;
+    char *pa_src;
+    int error = 0;
+    int *rerror = &error;
+    int r;
+    size_t pa_avail;
+    incr = 0;
+    wpos = hw->wpos;
 
-    if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) {
-        return NULL;
-    }
+    pa_threaded_mainloop_lock(pa->g->mainloop);
+    CHECK_DEAD_GOTO(pa->g, pa->stream, rerror, fail);
 
-    for (;;) {
-        int incr, to_grab, wpos;
+    size_t bytes_wanted = ((unsigned int)
+            (hw->samples - audio_pcm_hw_get_live_in(hw)) << hw->info.shift);
 
-        for (;;) {
-            if (pa->done) {
-                goto exit;
-            }
+    if (bytes_wanted == 0) {
+        /* no room */
+        goto bail;
+    }
 
-            if (pa->dead > 0) {
-                break;
-            }
+    size_t bytes_avail = pa_stream_readable_size(pa->stream);
 
-            if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) {
-                goto exit;
-            }
-        }
+    if (bytes_wanted > bytes_avail) {
+        bytes_wanted = bytes_avail;
+    }
 
-        incr = to_grab = audio_MIN (pa->dead, pa->g->conf.samples >> 2);
-        wpos = pa->wpos;
+    while (bytes_wanted) {
+        r = pa_stream_peek(pa->stream, (const void **)&pa_src, &pa_avail);
+        CHECK_SUCCESS_GOTO(pa->g, rerror, r == 0, fail);
 
-        if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) {
-            return NULL;
+        if (pa_avail == 0 || pa_avail > bytes_wanted) {
+            break;
         }
 
-        while (to_grab) {
-            int error;
-            int chunk = audio_MIN (to_grab, hw->samples - wpos);
-            void *buf = advance (pa->pcm_buf, wpos);
+        bytes_wanted -= pa_avail;
 
-            if (qpa_simple_read (pa, buf,
-                                 chunk << hw->info.shift, &error) < 0) {
-                qpa_logerr (error, "pa_simple_read failed\n");
-                return NULL;
-            }
-
-            hw->conv (hw->conv_buf + wpos, buf, chunk);
+        while (pa_avail) {
+            int chunk = audio_MIN(
+                    (int)(pa_avail >> hw->info.shift), hw->samples - wpos);
+            hw->conv(hw->conv_buf + wpos, pa_src, chunk);
             wpos = (wpos + chunk) % hw->samples;
-            to_grab -= chunk;
-        }
-
-        if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) {
-            return NULL;
+            pa_src += chunk << hw->info.shift;
+            pa_avail -= chunk << hw->info.shift;
+            incr += chunk;
         }
 
-        pa->wpos = wpos;
-        pa->dead -= incr;
-        pa->incr += incr;
+        r = pa_stream_drop(pa->stream);
+        CHECK_SUCCESS_GOTO(pa->g, rerror, r == 0, fail);
     }
 
- exit:
-    audio_pt_unlock (&pa->pt, AUDIO_FUNC);
-    return NULL;
-}
-
-static int qpa_run_in (HWVoiceIn *hw)
-{
-    int live, incr, dead;
-    PAVoiceIn *pa = (PAVoiceIn *) hw;
-
-    if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) {
-        return 0;
-    }
+bail:
+    pa_threaded_mainloop_unlock(pa->g->mainloop);
 
-    live = audio_pcm_hw_get_live_in (hw);
-    dead = hw->samples - live;
-    incr = audio_MIN (dead, pa->incr);
-    pa->incr -= incr;
-    pa->dead = dead - incr;
-    hw->wpos = pa->wpos;
-    if (pa->dead > 0) {
-        audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC);
-    }
-    else {
-        audio_pt_unlock (&pa->pt, AUDIO_FUNC);
-    }
+    hw->wpos = wpos;
     return incr;
+
+fail:
+    qpa_logerr(error, "qpa_run_in failed\n");
+    goto bail;
 }
 
 static int qpa_read (SWVoiceIn *sw, void *buf, int len)
@@ -470,13 +319,6 @@ static void stream_state_cb (pa_stream *s, void * userdata)
     }
 }
 
-static void stream_request_cb (pa_stream *s, size_t length, void *userdata)
-{
-    paaudio *g = userdata;
-
-    pa_threaded_mainloop_signal (g->mainloop, 0);
-}
-
 static pa_stream *qpa_simple_new (
         paaudio *g,
         const char *name,
@@ -498,23 +340,17 @@ static pa_stream *qpa_simple_new (
     }
 
     pa_stream_set_state_callback (stream, stream_state_cb, g);
-    pa_stream_set_read_callback (stream, stream_request_cb, g);
-    pa_stream_set_write_callback (stream, stream_request_cb, g);
 
     if (dir == PA_STREAM_PLAYBACK) {
-        r = pa_stream_connect_playback (stream, dev, attr,
-                                        PA_STREAM_INTERPOLATE_TIMING
-#ifdef PA_STREAM_ADJUST_LATENCY
-                                        |PA_STREAM_ADJUST_LATENCY
-#endif
-                                        |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);
+        r = pa_stream_connect_playback(stream, dev, attr,
+                PA_STREAM_INTERPOLATE_TIMING
+                | (g->conf.adjust_latency_out ? PA_STREAM_ADJUST_LATENCY : 0)
+                | PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);
     } else {
-        r = pa_stream_connect_record (stream, dev, attr,
-                                      PA_STREAM_INTERPOLATE_TIMING
-#ifdef PA_STREAM_ADJUST_LATENCY
-                                      |PA_STREAM_ADJUST_LATENCY
-#endif
-                                      |PA_STREAM_AUTO_TIMING_UPDATE);
+        r = pa_stream_connect_record(stream, dev, attr,
+                PA_STREAM_INTERPOLATE_TIMING
+                | (g->conf.adjust_latency_in ? PA_STREAM_ADJUST_LATENCY : 0)
+                | PA_STREAM_AUTO_TIMING_UPDATE);
     }
 
     if (r < 0) {
@@ -541,165 +377,167 @@ 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;
 
-    ss.format = audfmt_to_pa (as->fmt, as->endianness);
-    ss.channels = as->nchannels;
-    ss.rate = as->freq;
-
-    /*
-     * qemu audio tick runs at 100 Hz (by default), so processing
-     * data chunks worth 10 ms of sound should be a good fit.
-     */
-    ba.tlength = pa_usec_to_bytes (10 * 1000, &ss);
-    ba.minreq = pa_usec_to_bytes (5 * 1000, &ss);
-    ba.maxlength = -1;
-    ba.prebuf = -1;
-
-    obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness);
-
-    pa->stream = qpa_simple_new (
-        g,
-        "qemu",
-        PA_STREAM_PLAYBACK,
-        g->conf.sink,
-        &ss,
-        NULL,                   /* channel map */
-        &ba,                    /* buffering attributes */
-        &error
-        );
+    int64_t timer_tick_duration =
+        audio_MAX(audio_get_timer_ticks(), 1 * SCALE_MS);
+    int64_t frames_per_tick_x1000 =
+        ((timer_tick_duration * as->freq * 1000LL) / NANOSECONDS_PER_SECOND);
+
+    int64_t tlength = g->conf.tlength;
+    if (tlength == 0) {
+        tlength = (frames_per_tick_x1000) / 400;
+    }
+    int64_t buflen = g->conf.buffer_size_out;
+    if (buflen == 0) {
+        buflen = frames_per_tick_x1000  / 400;
+    }
+
+    ldebug("tick duration: %.2f ms (%.3f frames)\n",
+           ((float)timer_tick_duration) / SCALE_MS,
+           (float)frames_per_tick_x1000 / 1000.0f);
+
+    ldebug("OUT internal buffer: %.2f ms (%"PRId64" frames)\n",
+           buflen * (1000.0f / as->freq),
+           buflen);
+
+    ldebug("OUT tlength: %.2f ms (%"PRId64" frames)\n",
+           tlength * (1000.0f / as->freq),
+           tlength);
+
+    ldebug("OUT adjust latency: %s\n",
+           g->conf.adjust_latency_out ? "yes" : "no");
+
+    pa->ss.format = audfmt_to_pa(as->fmt, as->endianness);
+    pa->ss.channels = as->nchannels;
+    pa->ss.rate = as->freq;
+
+    pa->ba.tlength = tlength * pa_frame_size(&pa->ss);
+    pa->ba.maxlength = -1;
+    pa->ba.minreq = -1;
+    pa->ba.prebuf = -1;
+
+    obt_as.fmt = pa_to_audfmt(pa->ss.format, &obt_as.endianness);
+
+    pa->stream = qpa_simple_new(
+            g,
+            "qemu",
+            PA_STREAM_PLAYBACK,
+            g->conf.sink,
+            &pa->ss,
+            NULL,                   /* channel map */
+            &pa->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 = g->conf.samples;
-    pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
-    pa->rpos = hw->rpos;
-    if (!pa->pcm_buf) {
-        dolog ("Could not allocate buffer (%d bytes)\n",
-               hw->samples << hw->info.shift);
-        goto fail2;
-    }
-
-    if (audio_pt_init (&pa->pt, qpa_thread_out, hw, AUDIO_CAP, AUDIO_FUNC)) {
-        goto fail3;
-    }
+    audio_pcm_init_info(&hw->info, &obt_as);
+    hw->samples = buflen;
 
     return 0;
 
- fail3:
-    g_free (pa->pcm_buf);
-    pa->pcm_buf = NULL;
- fail2:
-    if (pa->stream) {
-        pa_stream_unref (pa->stream);
-        pa->stream = NULL;
-    }
- fail1:
+fail1:
     return -1;
 }
 
 static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
 {
     int error;
-    pa_sample_spec ss;
     struct audsettings obt_as = *as;
     PAVoiceIn *pa = (PAVoiceIn *) hw;
     paaudio *g = pa->g = drv_opaque;
 
-    ss.format = audfmt_to_pa (as->fmt, as->endianness);
-    ss.channels = as->nchannels;
-    ss.rate = as->freq;
-
-    obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness);
-
-    pa->stream = qpa_simple_new (
-        g,
-        "qemu",
-        PA_STREAM_RECORD,
-        g->conf.source,
-        &ss,
-        NULL,                   /* channel map */
-        NULL,                   /* buffering attributes */
-        &error
-        );
+    int64_t timer_tick_duration =
+        audio_MAX(audio_get_timer_ticks(), 1 * SCALE_MS);
+    int64_t frames_per_tick_x1000 =
+        ((timer_tick_duration * as->freq * 1000LL) / NANOSECONDS_PER_SECOND);
+
+    int64_t fragsize = g->conf.fragsize;
+    if (fragsize == 0) {
+        fragsize = frames_per_tick_x1000  / 2500;
+    }
+    int64_t buflen = g->conf.buffer_size_in;
+    if (buflen == 0) {
+        buflen = frames_per_tick_x1000  / 400;
+    }
+    int64_t maxlength = g->conf.maxlength_in;
+    if (maxlength == 0) {
+        maxlength = fragsize * 4;
+    }
+
+    ldebug("IN internal buffer: %.2f ms (%"PRId64" frames)\n",
+           buflen * (1000.0f / as->freq),
+           buflen);
+
+    ldebug("IN fragsize: %.2f ms (%"PRId64" frames)\n",
+           fragsize * (1000.0f / as->freq),
+           fragsize);
+
+    ldebug("IN maxlength: %.2f ms (%"PRId64" frames)\n",
+           maxlength * (1000.0f / as->freq),
+           maxlength);
+
+    ldebug("IN adjust latency: %s\n",
+           g->conf.adjust_latency_in ? "yes" : "no");
+
+    pa->ss.format = audfmt_to_pa(as->fmt, as->endianness);
+    pa->ss.channels = as->nchannels;
+    pa->ss.rate = as->freq;
+
+    pa->ba.fragsize = fragsize * pa_frame_size(&pa->ss);
+    pa->ba.maxlength = maxlength * pa_frame_size(&pa->ss);
+    pa->ba.minreq = -1;
+    pa->ba.prebuf = -1;
+
+    obt_as.fmt = pa_to_audfmt(pa->ss.format, &obt_as.endianness);
+
+    pa->stream = qpa_simple_new(
+            g,
+            "qemu",
+            PA_STREAM_RECORD,
+            g->conf.source,
+            &pa->ss,
+            NULL,                   /* channel map */
+            &pa->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 = g->conf.samples;
-    pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
-    pa->wpos = hw->wpos;
-    if (!pa->pcm_buf) {
-        dolog ("Could not allocate buffer (%d bytes)\n",
-               hw->samples << hw->info.shift);
-        goto fail2;
-    }
-
-    if (audio_pt_init (&pa->pt, qpa_thread_in, hw, AUDIO_CAP, AUDIO_FUNC)) {
-        goto fail3;
-    }
+    audio_pcm_init_info(&hw->info, &obt_as);
+    hw->samples = buflen;
 
     return 0;
 
- fail3:
-    g_free (pa->pcm_buf);
-    pa->pcm_buf = NULL;
- fail2:
-    if (pa->stream) {
-        pa_stream_unref (pa->stream);
-        pa->stream = NULL;
-    }
- fail1:
+    fail1:
     return -1;
 }
 
 static void qpa_fini_out (HWVoiceOut *hw)
 {
-    void *ret;
     PAVoiceOut *pa = (PAVoiceOut *) hw;
 
-    audio_pt_lock (&pa->pt, AUDIO_FUNC);
-    pa->done = 1;
-    audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC);
-    audio_pt_join (&pa->pt, &ret, AUDIO_FUNC);
-
     if (pa->stream) {
         pa_stream_unref (pa->stream);
         pa->stream = NULL;
     }
-
-    audio_pt_fini (&pa->pt, AUDIO_FUNC);
-    g_free (pa->pcm_buf);
-    pa->pcm_buf = NULL;
 }
 
 static void qpa_fini_in (HWVoiceIn *hw)
 {
-    void *ret;
     PAVoiceIn *pa = (PAVoiceIn *) hw;
 
-    audio_pt_lock (&pa->pt, AUDIO_FUNC);
-    pa->done = 1;
-    audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC);
-    audio_pt_join (&pa->pt, &ret, AUDIO_FUNC);
-
     if (pa->stream) {
         pa_stream_unref (pa->stream);
         pa->stream = NULL;
     }
-
-    audio_pt_fini (&pa->pt, AUDIO_FUNC);
-    g_free (pa->pcm_buf);
-    pa->pcm_buf = NULL;
 }
 
 static int qpa_ctl_out (HWVoiceOut *hw, int cmd, ...)
@@ -809,7 +647,8 @@ static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...)
 
 /* common */
 static PAConf glob_conf = {
-    .samples = 4096,
+        .adjust_latency_out = 0,
+        .adjust_latency_in = 1,
 };
 
 static void *qpa_audio_init (void)
@@ -897,10 +736,46 @@ static void qpa_audio_fini (void *opaque)
 
 struct audio_option qpa_options[] = {
     {
-        .name  = "SAMPLES",
+        .name  = "BUFFER_SIZE_OUT",
+        .tag   = AUD_OPT_INT,
+        .valp  = &glob_conf.buffer_size_out,
+        .descr = "internal buffer size in frames for playback device"
+    },
+    {
+        .name  = "BUFFER_SIZE_IN",
+        .tag   = AUD_OPT_INT,
+        .valp  = &glob_conf.buffer_size_in,
+        .descr = "internal buffer size in frames for recording device"
+    },
+    {
+        .name  = "TLENGTH",
         .tag   = AUD_OPT_INT,
-        .valp  = &glob_conf.samples,
-        .descr = "buffer size in samples"
+        .valp  = &glob_conf.tlength,
+        .descr = "playback buffer target length in frames"
+    },
+    {
+        .name  = "FRAGSIZE",
+        .tag   = AUD_OPT_INT,
+        .valp  = &glob_conf.fragsize,
+        .descr = "fragment length of recording device in frames"
+    },
+    {
+        .name  = "MAXLENGTH_IN",
+        .tag   = AUD_OPT_INT,
+        .valp  = &glob_conf.maxlength_in,
+        .descr = "maximum length of PA recording buffer in frames"
+    },
+    {
+        .name  = "ADJUST_LATENCY_OUT",
+        .tag   = AUD_OPT_BOOL,
+        .valp  = &glob_conf.adjust_latency_out,
+        .descr = "instruct PA to adjust latency for playback device"
+    },
+    {
+        .name  = "ADJUST_LATENCY_IN",
+        .tag   = AUD_OPT_BOOL,
+        .valp  = &glob_conf.adjust_latency_in,
+        .descr = "instruct PA to adjust latency for recording device"
     },
     {
         .name  = "SERVER",
