From 27c55ba097430b8e245b2e0d2f2077580196ba0c Mon Sep 17 00:00:00 2001
From: Andrew Eikum <aeikum@codeweavers.com>
Date: Fri, 3 Jan 2020 18:14:03 +1100
Subject: [PATCH] winepulse: Don't rely on pulseaudio callbacks for timing

---
 dlls/winepulse.drv/mmdevdrv.c | 521 +++++++++++++++-------------------
 1 file changed, 236 insertions(+), 285 deletions(-)

diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c
index ef9fc34e405..7d6327d2fe8 100644
--- a/dlls/winepulse.drv/mmdevdrv.c
+++ b/dlls/winepulse.drv/mmdevdrv.c
@@ -172,13 +172,16 @@ struct ACImpl {
     EDataFlow dataflow;
     DWORD flags;
     AUDCLNT_SHAREMODE share;
-    HANDLE event;
+    HANDLE event, timer;
 
     INT32 locked;
-    UINT32 bufsize_frames, bufsize_bytes, capture_period, pad, started, peek_ofs, wri_offs_bytes, lcl_offs_bytes;
-    UINT32 tmp_buffer_bytes, held_bytes, peek_len, peek_buffer_len;
+    UINT32 bufsize_frames, real_bufsize_bytes, period_bytes;
+    UINT32 started, peek_ofs, read_offs_bytes, lcl_offs_bytes, pa_offs_bytes;
+    UINT32 tmp_buffer_bytes, held_bytes, peek_len, peek_buffer_len, pa_held_bytes;
     BYTE *local_buffer, *tmp_buffer, *peek_buffer;
     void *locked_ptr;
+    BOOL please_quit, just_started, just_underran;
+    pa_usec_t last_time, mmdev_period_usec;
 
     pa_stream *stream;
     pa_sample_spec ss;
@@ -704,18 +707,7 @@ static void silence_buffer(pa_sample_format_t format, BYTE *buffer, UINT32 bytes
     memset(buffer, format == PA_SAMPLE_U8 ? 0x80 : 0, bytes);
 }
 
-static void pulse_free_noop(void *buf)
-{
-}
-
-enum write_buffer_flags
-{
-    WINEPULSE_WRITE_NOFREE = 0x01,
-    WINEPULSE_WRITE_SILENT = 0x02
-};
-
-static int write_buffer(const ACImpl *This, BYTE *buffer, UINT32 bytes,
-                        enum write_buffer_flags flags)
+static int write_buffer(const ACImpl *This, BYTE *buffer, UINT32 bytes)
 {
     float vol[PA_CHANNELS_MAX];
     BOOL adjust = FALSE;
@@ -723,7 +715,7 @@ static int write_buffer(const ACImpl *This, BYTE *buffer, UINT32 bytes,
     BYTE *end;
 
     if (!bytes) return 0;
-    if (This->session->mute || (flags & WINEPULSE_WRITE_SILENT))
+    if (This->session->mute)
     {
         silence_buffer(This->ss.format, buffer, bytes);
         goto write;
@@ -850,9 +842,7 @@ static int write_buffer(const ACImpl *This, BYTE *buffer, UINT32 bytes,
     }
 
 write:
-    return pa_stream_write(This->stream, buffer, bytes,
-                           (flags & WINEPULSE_WRITE_NOFREE) ? pulse_free_noop : NULL,
-                           0, PA_SEEK_RELATIVE);
+    return pa_stream_write(This->stream, buffer, bytes, NULL, 0, PA_SEEK_RELATIVE);
 }
 
 static void dump_attr(const pa_buffer_attr *attr) {
@@ -875,107 +865,69 @@ static void pulse_attr_update(pa_stream *s, void *user) {
     dump_attr(attr);
 }
 
-/* Here's the buffer setup:
- *
- *  vvvvvvvv sent to HW already
- *          vvvvvvvv in Pulse buffer but rewindable
- * [dddddddddddddddd] Pulse buffer
- *         [dddddddddddddddd--------] mmdevapi buffer
- *          ^^^^^^^^^^^^^^^^ pad
- *                  ^ lcl_offs_bytes
- *                  ^^^^^^^^^ held_bytes
- *                          ^ wri_offs_bytes
- *
- * GetCurrentPadding is pad
- *
- * During pulse_wr_callback, we decrement pad, fill Pulse buffer, and move
- *   lcl_offs forward
- *
- * During Stop, we flush the Pulse buffer
- */
-static void pulse_wr_callback(pa_stream *s, size_t bytes, void *userdata)
+static void pulse_write(ACImpl *This)
 {
-    ACImpl *This = userdata;
-    UINT32 oldpad = This->pad;
-
-    if(This->local_buffer){
-        UINT32 to_write;
-        BYTE *buf = This->local_buffer + This->lcl_offs_bytes;
-
-        if(This->pad > bytes){
-            This->clock_written += bytes;
-            This->pad -= bytes;
-        }else{
-            This->clock_written += This->pad;
-            This->pad = 0;
-        }
+    /* write as much data to PA as we can */
+    UINT32 to_write;
+    BYTE *buf = This->local_buffer + This->pa_offs_bytes;
+    UINT32 bytes = pa_stream_writable_size(This->stream);
 
-        bytes = min(bytes, This->held_bytes);
-
-        if(This->lcl_offs_bytes + bytes > This->bufsize_bytes){
-            to_write = This->bufsize_bytes - This->lcl_offs_bytes;
-            TRACE("writing small chunk of %u bytes\n", to_write);
-            write_buffer(This, buf, to_write, 0);
-            This->held_bytes -= to_write;
-            to_write = bytes - to_write;
-            This->lcl_offs_bytes = 0;
-            buf = This->local_buffer;
-        }else
-            to_write = bytes;
-
-        TRACE("writing main chunk of %u bytes\n", to_write);
-        write_buffer(This, buf, to_write, 0);
-        This->lcl_offs_bytes += to_write;
-        This->lcl_offs_bytes %= This->bufsize_bytes;
-        This->held_bytes -= to_write;
-    }else{
-        if (bytes < This->bufsize_bytes)
-            This->pad = This->bufsize_bytes - bytes;
-        else
-            This->pad = 0;
-
-        if (oldpad == This->pad)
-            return;
-
-        assert(oldpad > This->pad);
+    if(This->just_underran){
+        /* prebuffer with silence if needed */
+        if(This->pa_held_bytes < bytes){
+            to_write = bytes - This->pa_held_bytes;
+            TRACE("prebuffering %u frames of silence\n",
+                    (int)(to_write / pa_frame_size(&This->ss)));
+            buf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, to_write);
+            pa_stream_write(This->stream, buf, to_write, NULL, 0, PA_SEEK_RELATIVE);
+            HeapFree(GetProcessHeap(), 0, buf);
+        }
 
-        This->clock_written += oldpad - This->pad;
-        TRACE("New pad: %zu (-%zu)\n", This->pad / pa_frame_size(&This->ss), (oldpad - This->pad) / pa_frame_size(&This->ss));
+        This->just_underran = FALSE;
     }
 
-    if (This->event)
-        SetEvent(This->event);
-}
+    buf = This->local_buffer + This->pa_offs_bytes;
+    TRACE("held: %u, avail: %u\n",
+            This->pa_held_bytes, bytes);
+    bytes = min(This->pa_held_bytes, bytes);
+
+    if(This->pa_offs_bytes + bytes > This->real_bufsize_bytes){
+        to_write = This->real_bufsize_bytes - This->pa_offs_bytes;
+        TRACE("writing small chunk of %u bytes\n", to_write);
+        write_buffer(This, buf, to_write);
+        This->pa_held_bytes -= to_write;
+        to_write = bytes - to_write;
+        This->pa_offs_bytes = 0;
+        buf = This->local_buffer;
+    }else
+        to_write = bytes;
 
-static void pulse_underflow_callback(pa_stream *s, void *userdata)
-{
-    WARN("Underflow\n");
+    TRACE("writing main chunk of %u bytes\n", to_write);
+    write_buffer(This, buf, to_write);
+    This->pa_offs_bytes += to_write;
+    This->pa_offs_bytes %= This->real_bufsize_bytes;
+    This->pa_held_bytes -= to_write;
 }
 
-/* Latency is periodically updated even when nothing is played,
- * because of PA_STREAM_AUTO_TIMING_UPDATE so use it as timer
- *
- * Perfect for passing all tests :)
- */
-static void pulse_latency_callback(pa_stream *s, void *userdata)
+static void pulse_underflow_callback(pa_stream *s, void *userdata)
 {
     ACImpl *This = userdata;
-    if (!This->pad && This->event)
-        SetEvent(This->event);
+    WARN("%p: Underflow\n", userdata);
+    This->just_underran = TRUE;
 }
 
 static void pulse_started_callback(pa_stream *s, void *userdata)
 {
-    TRACE("(Re)started playing\n");
+    TRACE("%p: (Re)started playing\n", userdata);
 }
 
 static void pulse_rd_loop(ACImpl *This, size_t bytes)
 {
-    while (bytes >= This->capture_period) {
+    while (bytes >= This->period_bytes) {
         ACPacket *p, *next;
         LARGE_INTEGER stamp, freq;
         BYTE *dst, *src;
-        size_t src_len, copy, rem = This->capture_period;
+        size_t src_len, copy, rem = This->period_bytes;
         if (!(p = (ACPacket*)list_head(&This->packet_free_head))) {
             p = (ACPacket*)list_head(&This->packet_filled_head);
             if (!p->discont) {
@@ -983,11 +935,8 @@ static void pulse_rd_loop(ACImpl *This, size_t bytes)
                 next->discont = 1;
             } else
                 p = (ACPacket*)list_tail(&This->packet_filled_head);
-            assert(This->pad == This->bufsize_bytes);
         } else {
-            assert(This->pad < This->bufsize_bytes);
-            This->pad += This->capture_period;
-            assert(This->pad <= This->bufsize_bytes);
+            This->held_bytes += This->period_bytes;
         }
         QueryPerformanceCounter(&stamp);
         QueryPerformanceFrequency(&freq);
@@ -1008,12 +957,14 @@ static void pulse_rd_loop(ACImpl *This, size_t bytes)
                 This->peek_ofs += copy;
                 if(This->peek_len == This->peek_ofs)
                     This->peek_len = 0;
-            } else {
-                pa_stream_peek(This->stream, (const void**)&src, &src_len);
+            } else if (pa_stream_peek(This->stream, (const void**)&src, &src_len) == 0 && src_len) {
 
                 copy = min(rem, src_len);
 
-                memcpy(dst, src, rem);
+                if(src)
+                    memcpy(dst, src, copy);
+                else
+                    silence_buffer(This->ss.format, dst, copy);
 
                 dst += copy;
                 rem -= copy;
@@ -1025,7 +976,11 @@ static void pulse_rd_loop(ACImpl *This, size_t bytes)
                         This->peek_buffer_len = src_len;
                     }
 
-                    memcpy(This->peek_buffer, src + copy, src_len - copy);
+                    if(src)
+                        memcpy(This->peek_buffer, src + copy, src_len - copy);
+                    else
+                        silence_buffer(This->ss.format, This->peek_buffer, src_len - copy);
+
                     This->peek_len = src_len - copy;
                     This->peek_ofs = 0;
                 }
@@ -1034,21 +989,18 @@ static void pulse_rd_loop(ACImpl *This, size_t bytes)
             }
         }
 
-        bytes -= This->capture_period;
+        bytes -= This->period_bytes;
     }
 }
 
 static void pulse_rd_drop(ACImpl *This, size_t bytes)
 {
-    while (bytes >= This->capture_period) {
-        size_t src_len, copy, rem = This->capture_period;
+    while (bytes >= This->period_bytes) {
+        size_t src_len, copy, rem = This->period_bytes;
         while (rem) {
             const void *src;
             pa_stream_peek(This->stream, &src, &src_len);
-            assert(src_len);
-            assert(This->peek_ofs < src_len);
             src_len -= This->peek_ofs;
-            assert(src_len <= bytes);
 
             copy = rem;
             if (copy > src_len)
@@ -1067,23 +1019,95 @@ static void pulse_rd_drop(ACImpl *This, size_t bytes)
     }
 }
 
-static void pulse_rd_callback(pa_stream *s, size_t bytes, void *userdata)
+static void pulse_read(ACImpl *This)
 {
-    ACImpl *This = userdata;
-
-    TRACE("Readable total: %zu, fragsize: %u\n", bytes, pa_stream_get_buffer_attr(s)->fragsize);
-    assert(bytes >= This->peek_ofs);
+    size_t bytes = pa_stream_readable_size(This->stream);
+    TRACE("Readable total: %zu, fragsize: %u\n", bytes, pa_stream_get_buffer_attr(This->stream)->fragsize);
     bytes -= This->peek_ofs;
-    if (bytes < This->capture_period)
+    if (bytes < This->period_bytes)
         return;
 
     if (This->started)
         pulse_rd_loop(This, bytes);
     else
         pulse_rd_drop(This, bytes);
+}
+
+static DWORD WINAPI pulse_timer_cb(void *user)
+{
+    DWORD delay;
+    UINT32 adv_bytes;
+    ACImpl *This = user;
+
+    pthread_mutex_lock(&pulse_lock);
+    delay = This->mmdev_period_usec / 1000;
+    pa_stream_get_time(This->stream, &This->last_time);
+    pthread_mutex_unlock(&pulse_lock);
+
+    while(!This->please_quit){
+        pa_usec_t now, adv_usec = 0;
+        int err;
+
+        Sleep(delay);
+
+        pthread_mutex_lock(&pulse_lock);
+
+        delay = This->mmdev_period_usec / 1000;
+
+        err = pa_stream_get_time(This->stream, &now);
+        if(err == 0){
+            TRACE("got now: %s, last time: %s\n", wine_dbgstr_longlong(now), wine_dbgstr_longlong(This->last_time));
+            if(This->started && (This->dataflow == eCapture || This->held_bytes)){
+                if(This->just_started){
+                    /* let it play out a period to absorb some latency and get accurate timing */
+                    pa_usec_t diff = now - This->last_time;
+
+                    if(diff > This->mmdev_period_usec){
+                        This->just_started = FALSE;
+                        This->last_time = now;
+                    }
+                }else{
+                    INT32 adjust = This->last_time + This->mmdev_period_usec - now;
+
+                    adv_usec = now - This->last_time;
+
+                    if(adjust > ((INT32)(This->mmdev_period_usec / 2)))
+                        adjust = This->mmdev_period_usec / 2;
+                    else if(adjust < -((INT32)(This->mmdev_period_usec / 2)))
+                        adjust = -1 * This->mmdev_period_usec / 2;
+
+                    delay = (This->mmdev_period_usec + adjust) / 1000;
+
+                    This->last_time += This->mmdev_period_usec;
+                }
+
+                if(This->dataflow == eRender){
+                    pulse_write(This);
+
+                    /* regardless of what PA does, advance one period */
+                    adv_bytes = min(This->period_bytes, This->held_bytes);
+                    This->lcl_offs_bytes += adv_bytes;
+                    This->held_bytes -= adv_bytes;
+                }else if(This->dataflow == eCapture){
+                    pulse_read(This);
+                }
+            }else{
+                This->last_time = now;
+                delay = This->mmdev_period_usec / 1000;
+            }
+        }
+
+        if (This->event)
+            SetEvent(This->event);
 
-    if (This->event)
-        SetEvent(This->event);
+        TRACE("%p after update, adv usec: %d, held: %u, delay: %u\n",
+                This, (int)adv_usec,
+                (int)(This->held_bytes/ pa_frame_size(&This->ss)), delay);
+
+        pthread_mutex_unlock(&pulse_lock);
+    }
+
+    return 0;
 }
 
 static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) {
@@ -1112,15 +1136,16 @@ static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) {
 
     /* PulseAudio will fill in correct values */
     attr.minreq = attr.fragsize = period_bytes;
-    attr.maxlength = attr.tlength = This->bufsize_bytes;
+    attr.tlength = period_bytes * 3;
+    attr.maxlength = This->bufsize_frames * pa_frame_size(&This->ss);
     attr.prebuf = pa_frame_size(&This->ss);
     dump_attr(&attr);
     if (This->dataflow == eRender)
         ret = pa_stream_connect_playback(This->stream, NULL, &attr,
-        PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS, NULL, NULL);
+        PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_ADJUST_LATENCY, NULL, NULL);
     else
         ret = pa_stream_connect_record(This->stream, NULL, &attr,
-        PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS);
+        PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_ADJUST_LATENCY);
     if (ret < 0) {
         WARN("Returns %i\n", ret);
         return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
@@ -1131,11 +1156,9 @@ static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) {
         return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
 
     if (This->dataflow == eRender) {
-        pa_stream_set_write_callback(This->stream, pulse_wr_callback, This);
         pa_stream_set_underflow_callback(This->stream, pulse_underflow_callback, This);
         pa_stream_set_started_callback(This->stream, pulse_started_callback, This);
-    } else
-        pa_stream_set_read_callback(This->stream, pulse_rd_callback, This);
+    }
     return S_OK;
 }
 
@@ -1270,6 +1293,11 @@ static ULONG WINAPI AudioClient_Release(IAudioClient *iface)
     TRACE("(%p) Refcount now %u\n", This, ref);
     if (!ref) {
         if (This->stream) {
+            if(This->timer){
+                This->please_quit = TRUE;
+                WaitForSingleObject(This->timer, INFINITE);
+                CloseHandle(This->timer);
+            }
             pthread_mutex_lock(&pulse_lock);
             if (PA_STREAM_IS_GOOD(pa_stream_get_state(This->stream))) {
                 pa_stream_disconnect(This->stream);
@@ -1565,7 +1593,7 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface,
 {
     ACImpl *This = impl_from_IAudioClient(iface);
     HRESULT hr = S_OK;
-    UINT period_bytes;
+    UINT32 bufsize_bytes;
 
     TRACE("(%p)->(%x, %x, %s, %s, %p, %s)\n", This, mode, flags,
           wine_dbgstr_longlong(duration), wine_dbgstr_longlong(period), fmt, debugstr_guid(sessionguid));
@@ -1610,38 +1638,19 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface,
     if (FAILED(hr))
         goto exit;
 
-    if (mode == AUDCLNT_SHAREMODE_SHARED) {
-        REFERENCE_TIME def = pulse_def_period[This->dataflow == eCapture];
-        REFERENCE_TIME min = pulse_min_period[This->dataflow == eCapture];
-
-        /* Switch to low latency mode if below 2 default periods,
-         * which is 20 ms by default, this will increase the amount
-         * of interrupts but allows very low latency. In dsound I
-         * managed to get a total latency of ~8ms, which is well below
-         * default
-         */
-        if (duration < 2 * def)
-            period = min;
-        else
-            period = def;
-        if (duration < 2 * period)
-            duration = 2 * period;
+    period = pulse_def_period[This->dataflow == eCapture];
+    if (duration < 3 * period)
+        duration = 3 * period;
 
-        /* Uh oh, really low latency requested.. */
-        if (duration <= 2 * period)
-            period /= 2;
-    }
-    period_bytes = pa_frame_size(&This->ss) * MulDiv(period, This->ss.rate, 10000000);
+    This->period_bytes = pa_frame_size(&This->ss) * MulDiv(period, This->ss.rate, 10000000);
 
-    if (duration < 20000000)
-        This->bufsize_frames = ceil((duration / 10000000.) * fmt->nSamplesPerSec);
-    else
-        This->bufsize_frames = 2 * fmt->nSamplesPerSec;
-    This->bufsize_bytes = This->bufsize_frames * pa_frame_size(&This->ss);
+    This->bufsize_frames = ceil((duration / 10000000.) * fmt->nSamplesPerSec);
+    bufsize_bytes = This->bufsize_frames * pa_frame_size(&This->ss);
+    This->mmdev_period_usec = period / 10;
 
     This->share = mode;
     This->flags = flags;
-    hr = pulse_stream_connect(This, period_bytes);
+    hr = pulse_stream_connect(This, This->period_bytes);
     if (SUCCEEDED(hr)) {
         UINT32 unalign;
         const pa_buffer_attr *attr = pa_stream_get_buffer_attr(This->stream);
@@ -1649,39 +1658,34 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface,
         /* Update frames according to new size */
         dump_attr(attr);
         if (This->dataflow == eRender) {
-            if (attr->tlength < This->bufsize_bytes) {
-                TRACE("PulseAudio buffer too small (%u < %u), using tmp buffer\n", attr->tlength, This->bufsize_bytes);
-
-                This->local_buffer = HeapAlloc(GetProcessHeap(), 0, This->bufsize_bytes);
-                if(!This->local_buffer)
-                    hr = E_OUTOFMEMORY;
-            }
+            This->real_bufsize_bytes = This->bufsize_frames * 2 * pa_frame_size(&This->ss);
+            This->local_buffer = HeapAlloc(GetProcessHeap(), 0, This->real_bufsize_bytes);
+            if(!This->local_buffer)
+                hr = E_OUTOFMEMORY;
         } else {
             UINT32 i, capture_packets;
 
-            This->capture_period = period_bytes = attr->fragsize;
-            if ((unalign = This->bufsize_bytes % period_bytes))
-                This->bufsize_bytes += period_bytes - unalign;
-            This->bufsize_frames = This->bufsize_bytes / pa_frame_size(&This->ss);
+            if ((unalign = bufsize_bytes % This->period_bytes))
+                bufsize_bytes += This->period_bytes - unalign;
+            This->bufsize_frames = bufsize_bytes / pa_frame_size(&This->ss);
+            This->real_bufsize_bytes = bufsize_bytes;
 
-            capture_packets = This->bufsize_bytes / This->capture_period;
+            capture_packets = This->real_bufsize_bytes / This->period_bytes;
 
-            This->local_buffer = HeapAlloc(GetProcessHeap(), 0, This->bufsize_bytes + capture_packets * sizeof(ACPacket));
+            This->local_buffer = HeapAlloc(GetProcessHeap(), 0, This->real_bufsize_bytes + capture_packets * sizeof(ACPacket));
             if (!This->local_buffer)
                 hr = E_OUTOFMEMORY;
             else {
-                ACPacket *cur_packet = (ACPacket*)((char*)This->local_buffer + This->bufsize_bytes);
+                ACPacket *cur_packet = (ACPacket*)((char*)This->local_buffer + This->real_bufsize_bytes);
                 BYTE *data = This->local_buffer;
-                silence_buffer(This->ss.format, This->local_buffer, This->bufsize_bytes);
+                silence_buffer(This->ss.format, This->local_buffer, This->real_bufsize_bytes);
                 list_init(&This->packet_free_head);
                 list_init(&This->packet_filled_head);
                 for (i = 0; i < capture_packets; ++i, ++cur_packet) {
                     list_add_tail(&This->packet_free_head, &cur_packet->entry);
                     cur_packet->data = data;
-                    data += This->capture_period;
+                    data += This->period_bytes;
                 }
-                assert(!This->capture_period || This->bufsize_bytes == This->capture_period * capture_packets);
-                assert(!capture_packets || data - This->bufsize_bytes == This->local_buffer);
             }
         }
     }
@@ -1746,12 +1750,12 @@ static HRESULT WINAPI AudioClient_GetStreamLatency(IAudioClient *iface,
     attr = pa_stream_get_buffer_attr(This->stream);
     if (This->dataflow == eRender){
         lat = attr->minreq / pa_frame_size(&This->ss);
-        lat += pulse_def_period[0];
     }else
         lat = attr->fragsize / pa_frame_size(&This->ss);
     *latency = 10000000;
     *latency *= lat;
     *latency /= This->ss.rate;
+    *latency += pulse_def_period[0];
     pthread_mutex_unlock(&pulse_lock);
     TRACE("Latency: %u ms\n", (DWORD)(*latency / 10000));
     return S_OK;
@@ -1759,7 +1763,7 @@ static HRESULT WINAPI AudioClient_GetStreamLatency(IAudioClient *iface,
 
 static void ACImpl_GetRenderPad(ACImpl *This, UINT32 *out)
 {
-    *out = This->pad / pa_frame_size(&This->ss);
+    *out = This->held_bytes / pa_frame_size(&This->ss);
 }
 
 static void ACImpl_GetCapturePad(ACImpl *This, UINT32 *out)
@@ -1771,7 +1775,7 @@ static void ACImpl_GetCapturePad(ACImpl *This, UINT32 *out)
         list_remove(&packet->entry);
     }
     if (out)
-        *out = This->pad / pa_frame_size(&This->ss);
+        *out = This->held_bytes / pa_frame_size(&This->ss);
 }
 
 static HRESULT WINAPI AudioClient_GetCurrentPadding(IAudioClient *iface,
@@ -2017,6 +2021,8 @@ static HRESULT WINAPI AudioClient_Start(IAudioClient *iface)
         return AUDCLNT_E_NOT_STOPPED;
     }
 
+    pulse_write(This);
+
     if (pa_stream_is_corked(This->stream)) {
         o = pa_stream_cork(This->stream, 0, pulse_op_cb, &success);
         if (o) {
@@ -2031,8 +2037,10 @@ static HRESULT WINAPI AudioClient_Start(IAudioClient *iface)
 
     if (SUCCEEDED(hr)) {
         This->started = TRUE;
-        if (This->dataflow == eRender && This->event)
-            pa_stream_set_latency_update_callback(This->stream, pulse_latency_callback, This);
+        This->just_started = TRUE;
+
+        if(!This->timer)
+            This->timer = CreateThread(NULL, 0, pulse_timer_cb, This, 0, NULL);
     }
     pthread_mutex_unlock(&pulse_lock);
     return hr;
@@ -2104,7 +2112,7 @@ static HRESULT WINAPI AudioClient_Reset(IAudioClient *iface)
     if (This->dataflow == eRender) {
         /* If there is still data in the render buffer it needs to be removed from the server */
         int success = 0;
-        if (This->pad) {
+        if (This->held_bytes) {
             pa_operation *o = pa_stream_flush(This->stream, pulse_op_cb, &success);
             if (o) {
                 while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
@@ -2112,14 +2120,14 @@ static HRESULT WINAPI AudioClient_Reset(IAudioClient *iface)
                 pa_operation_unref(o);
             }
         }
-        if (success || !This->pad){
-            This->clock_lastpos = This->clock_written = This->pad = 0;
-            This->wri_offs_bytes = This->lcl_offs_bytes = This->held_bytes = 0;
+        if (success || !This->held_bytes){
+            This->clock_lastpos = This->clock_written = 0;
+            This->pa_offs_bytes = This->lcl_offs_bytes = This->held_bytes = This->pa_held_bytes = 0;
         }
     } else {
         ACPacket *p;
-        This->clock_written += This->pad;
-        This->pad = 0;
+        This->clock_written += This->held_bytes;
+        This->held_bytes = 0;
 
         if ((p = This->locked_ptr)) {
             This->locked_ptr = NULL;
@@ -2285,10 +2293,9 @@ static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface,
         UINT32 frames, BYTE **data)
 {
     ACImpl *This = impl_from_IAudioRenderClient(iface);
-    size_t avail, req, bytes = frames * pa_frame_size(&This->ss);
-    UINT32 pad;
+    size_t bytes = frames * pa_frame_size(&This->ss);
     HRESULT hr = S_OK;
-    int ret = -1;
+    UINT32 wri_offs_bytes;
 
     TRACE("(%p)->(%u, %p)\n", This, frames, data);
 
@@ -2307,37 +2314,19 @@ static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface,
         return S_OK;
     }
 
-    ACImpl_GetRenderPad(This, &pad);
-    avail = This->bufsize_frames - pad;
-    if (avail < frames || bytes > This->bufsize_bytes) {
+    if(This->held_bytes / pa_frame_size(&This->ss) + frames > This->bufsize_frames){
         pthread_mutex_unlock(&pulse_lock);
-        WARN("Wanted to write %u, but only %zu available\n", frames, avail);
         return AUDCLNT_E_BUFFER_TOO_LARGE;
     }
 
-    if(This->local_buffer){
-        if(This->wri_offs_bytes + bytes > This->bufsize_bytes){
-            alloc_tmp_buffer(This, bytes);
-            *data = This->tmp_buffer;
-            This->locked = -frames;
-        }else{
-            *data = This->local_buffer + This->wri_offs_bytes;
-            This->locked = frames;
-        }
+    wri_offs_bytes = (This->lcl_offs_bytes + This->held_bytes) % This->real_bufsize_bytes;
+    if(wri_offs_bytes + bytes > This->real_bufsize_bytes){
+        alloc_tmp_buffer(This, bytes);
+        *data = This->tmp_buffer;
+        This->locked = -bytes;
     }else{
-        req = bytes;
-        ret = pa_stream_begin_write(This->stream, &This->locked_ptr, &req);
-        if (ret < 0 || req < bytes) {
-            FIXME("%p Not using pulse locked data: %i %zu/%u %u/%u\n", This, ret, req/pa_frame_size(&This->ss), frames, pad, This->bufsize_frames);
-            if (ret >= 0)
-                pa_stream_cancel_write(This->stream);
-            alloc_tmp_buffer(This, bytes);
-            *data = This->tmp_buffer;
-            This->locked_ptr = NULL;
-        } else
-            *data = This->locked_ptr;
-
-        This->locked = frames;
+        *data = This->local_buffer + wri_offs_bytes;
+        This->locked = bytes;
     }
 
     silence_buffer(This->ss.format, *data, bytes);
@@ -2349,12 +2338,13 @@ static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface,
 
 static void pulse_wrap_buffer(ACImpl *This, BYTE *buffer, UINT32 written_bytes)
 {
-    UINT32 chunk_bytes = This->bufsize_bytes - This->wri_offs_bytes;
+    UINT32 wri_offs_bytes = (This->lcl_offs_bytes + This->held_bytes) % This->real_bufsize_bytes;
+    UINT32 chunk_bytes = This->real_bufsize_bytes - wri_offs_bytes;
 
     if(written_bytes <= chunk_bytes){
-        memcpy(This->local_buffer + This->wri_offs_bytes, buffer, written_bytes);
+        memcpy(This->local_buffer + wri_offs_bytes, buffer, written_bytes);
     }else{
-        memcpy(This->local_buffer + This->wri_offs_bytes, buffer, chunk_bytes);
+        memcpy(This->local_buffer + wri_offs_bytes, buffer, chunk_bytes);
         memcpy(This->local_buffer, buffer + chunk_bytes,
                 written_bytes - chunk_bytes);
     }
@@ -2365,88 +2355,42 @@ static HRESULT WINAPI AudioRenderClient_ReleaseBuffer(
 {
     ACImpl *This = impl_from_IAudioRenderClient(iface);
     UINT32 written_bytes = written_frames * pa_frame_size(&This->ss);
+    BYTE *buffer;
 
     TRACE("(%p)->(%u, %x)\n", This, written_frames, flags);
 
     pthread_mutex_lock(&pulse_lock);
     if (!This->locked || !written_frames) {
-        if (This->locked_ptr)
-            pa_stream_cancel_write(This->stream);
         This->locked = 0;
-        This->locked_ptr = NULL;
         pthread_mutex_unlock(&pulse_lock);
         return written_frames ? AUDCLNT_E_OUT_OF_ORDER : S_OK;
     }
 
-    if (This->locked < written_frames) {
+    if(written_frames * pa_frame_size(&This->ss) > (This->locked >= 0 ? This->locked : -This->locked)){
         pthread_mutex_unlock(&pulse_lock);
         return AUDCLNT_E_INVALID_SIZE;
     }
 
-    if(This->local_buffer){
-        BYTE *buffer;
-
-        if(This->locked >= 0)
-            buffer = This->local_buffer + This->wri_offs_bytes;
-        else
-            buffer = This->tmp_buffer;
-
-        if(flags & AUDCLNT_BUFFERFLAGS_SILENT)
-            silence_buffer(This->ss.format, buffer, written_bytes);
-
-        if(This->locked < 0)
-            pulse_wrap_buffer(This, buffer, written_bytes);
-
-        This->wri_offs_bytes += written_bytes;
-        This->wri_offs_bytes %= This->bufsize_bytes;
-
-        This->pad += written_bytes;
-        This->held_bytes += written_bytes;
-
-        if(This->held_bytes == This->pad){
-            int e;
-            UINT32 to_write = min(This->attr.tlength, written_bytes);
-
-            /* nothing in PA, so send data immediately */
-
-            TRACE("pre-writing %u bytes\n", to_write);
-
-            e = write_buffer(This, buffer, to_write, 0);
-            if(e)
-                ERR("pa_stream_write failed: 0x%x\n", e);
-
-            This->lcl_offs_bytes += to_write;
-            This->lcl_offs_bytes %= This->bufsize_bytes;
-            This->held_bytes -= to_write;
-        }
-
-    }else{
-        enum write_buffer_flags wr_flags = 0;
+    if(This->locked >= 0)
+        buffer = This->local_buffer + (This->lcl_offs_bytes + This->held_bytes) % This->real_bufsize_bytes;
+    else
+        buffer = This->tmp_buffer;
 
-        if (flags & AUDCLNT_BUFFERFLAGS_SILENT) wr_flags |= WINEPULSE_WRITE_SILENT;
-        if (!This->locked_ptr) wr_flags |= WINEPULSE_WRITE_NOFREE;
+    if(flags & AUDCLNT_BUFFERFLAGS_SILENT)
+        silence_buffer(This->ss.format, buffer, written_bytes);
 
-        write_buffer(This, This->locked_ptr ? This->locked_ptr : This->tmp_buffer, written_bytes, wr_flags);
-        This->pad += written_bytes;
-    }
-
-    if (!pa_stream_is_corked(This->stream)) {
-        int success;
-        pa_operation *o;
-        o = pa_stream_trigger(This->stream, pulse_op_cb, &success);
-        if (o) {
-            while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
-                pthread_cond_wait(&pulse_cond, &pulse_lock);
-            pa_operation_unref(o);
-        }
-    }
+    if(This->locked < 0)
+        pulse_wrap_buffer(This, buffer, written_bytes);
 
+    This->held_bytes += written_bytes;
+    This->pa_held_bytes += written_bytes;
+    This->clock_written += written_bytes;
     This->locked = 0;
-    This->locked_ptr = NULL;
-    TRACE("Released %u, pad %zu\n", written_frames, This->pad / pa_frame_size(&This->ss));
-    assert(This->pad <= This->bufsize_bytes);
+
+    TRACE("Released %u, held %zu\n", written_frames, This->held_bytes / pa_frame_size(&This->ss));
 
     pthread_mutex_unlock(&pulse_lock);
+
     return S_OK;
 }
 
@@ -2523,13 +2467,13 @@ static HRESULT WINAPI AudioCaptureClient_GetBuffer(IAudioCaptureClient *iface,
 
     ACImpl_GetCapturePad(This, NULL);
     if ((packet = This->locked_ptr)) {
-        *frames = This->capture_period / pa_frame_size(&This->ss);
+        *frames = This->period_bytes / pa_frame_size(&This->ss);
         *flags = 0;
         if (packet->discont)
             *flags |= AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY;
         if (devpos) {
             if (packet->discont)
-                *devpos = (This->clock_written + This->capture_period) / pa_frame_size(&This->ss);
+                *devpos = (This->clock_written + This->period_bytes) / pa_frame_size(&This->ss);
             else
                 *devpos = This->clock_written / pa_frame_size(&This->ss);
         }
@@ -2563,11 +2507,11 @@ static HRESULT WINAPI AudioCaptureClient_ReleaseBuffer(
     if (done) {
         ACPacket *packet = This->locked_ptr;
         This->locked_ptr = NULL;
-        This->pad -= This->capture_period;
+        This->held_bytes -= This->period_bytes;
         if (packet->discont)
-            This->clock_written += 2 * This->capture_period;
+            This->clock_written += 2 * This->period_bytes;
         else
-            This->clock_written += This->capture_period;
+            This->clock_written += This->period_bytes;
         list_add_tail(&This->packet_free_head, &packet->entry);
     }
     This->locked = 0;
@@ -2587,7 +2531,7 @@ static HRESULT WINAPI AudioCaptureClient_GetNextPacketSize(
     pthread_mutex_lock(&pulse_lock);
     ACImpl_GetCapturePad(This, NULL);
     if (This->locked_ptr)
-        *frames = This->capture_period / pa_frame_size(&This->ss);
+        *frames = This->period_bytes / pa_frame_size(&This->ss);
     else
         *frames = 0;
     pthread_mutex_unlock(&pulse_lock);
@@ -2679,7 +2623,14 @@ static HRESULT WINAPI AudioClock_GetPosition(IAudioClock *iface, UINT64 *pos,
         return hr;
     }
 
-    *pos = This->clock_written;
+    *pos = This->clock_written - This->held_bytes;
+
+    if(This->started){
+        if(*pos < This->period_bytes)
+            *pos = 0;
+        else if(This->held_bytes > This->period_bytes)
+            *pos -= This->period_bytes;
+    }
 
     if (This->share == AUDCLNT_SHAREMODE_EXCLUSIVE)
         *pos /= pa_frame_size(&This->ss);
-- 
2.24.1

