[PATCH] winepulse: Don't rely on pulseaudio callbacks for timing

Andrew Eikum aeikum at codeweavers.com
Mon Aug 17 10:38:18 CDT 2020


Contains contributions from Mark Harmstone and Zhiyi Zhang.

Some devices, especially USB devices, have very irregular timing or
very long periods. Some Windows applications (The Witcher 3) are built
to expect regular, short period times, and so would underrun when the
device timing didn't match their expectations. This patch decouples
the Windows-side timing from the OS-side timing so we can meet those
applications' requirements.

Signed-off-by: Andrew Eikum <aeikum at codeweavers.com>
---

This patch has been in wine-staging and Proton for years. Finally
squashing down the fixes and submitting it upstream.

 dlls/winepulse.drv/mmdevdrv.c | 617 ++++++++++++++++------------------
 1 file changed, 281 insertions(+), 336 deletions(-)

diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c
index 23c34a50f66..e34c5ea5916 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;
@@ -709,18 +712,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;
@@ -728,7 +720,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;
@@ -855,9 +847,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) {
@@ -880,147 +870,129 @@ 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;
+    /* 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);
 
-    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;
+    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);
         }
 
-        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);
-
-        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;
+
+    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;
 }
 
 static void pulse_underflow_callback(pa_stream *s, void *userdata)
-{
-    WARN("Underflow\n");
-}
-
-/* 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)
 {
     ACImpl *This = userdata;
-    if (!This->pad && This->event)
-        SetEvent(This->event);
+    WARN("%p: Underflow\n", userdata);
+    This->just_underran = TRUE;
+    /* re-sync */
+    This->pa_offs_bytes = This->lcl_offs_bytes;
+    This->pa_held_bytes = This->held_bytes;
 }
 
 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)
+static void pulse_read(ACImpl *This)
 {
-    while (bytes >= This->capture_period) {
-        ACPacket *p, *next;
-        LARGE_INTEGER stamp, freq;
-        BYTE *dst, *src;
-        size_t src_len, copy, rem = This->capture_period;
-        if (!(p = (ACPacket*)list_head(&This->packet_free_head))) {
-            p = (ACPacket*)list_head(&This->packet_filled_head);
-            if (!p->discont) {
-                next = (ACPacket*)p->entry.next;
-                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);
+    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_len - This->peek_ofs;
+
+    while (bytes >= This->period_bytes) {
+        BYTE *dst = NULL, *src;
+        size_t src_len, copy, rem = This->period_bytes;
+
+        if (This->started) {
+            LARGE_INTEGER stamp, freq;
+            ACPacket *p, *next;
+
+            if (!(p = (ACPacket*)list_head(&This->packet_free_head))) {
+                p = (ACPacket*)list_head(&This->packet_filled_head);
+                if (!p) return;
+                if (!p->discont) {
+                    next = (ACPacket*)p->entry.next;
+                    next->discont = 1;
+                } else
+                    p = (ACPacket*)list_tail(&This->packet_filled_head);
+            } else {
+                This->held_bytes += This->period_bytes;
+            }
+            QueryPerformanceCounter(&stamp);
+            QueryPerformanceFrequency(&freq);
+            p->qpcpos = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart;
+            p->discont = 0;
+            list_remove(&p->entry);
+            list_add_tail(&This->packet_filled_head, &p->entry);
+
+            dst = p->data;
         }
-        QueryPerformanceCounter(&stamp);
-        QueryPerformanceFrequency(&freq);
-        p->qpcpos = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart;
-        p->discont = 0;
-        list_remove(&p->entry);
-        list_add_tail(&This->packet_filled_head, &p->entry);
 
-        dst = p->data;
         while (rem) {
             if (This->peek_len) {
                 copy = min(rem, This->peek_len - This->peek_ofs);
 
-                memcpy(dst, This->peek_buffer + This->peek_ofs, copy);
+                if (dst) {
+                    memcpy(dst, This->peek_buffer + This->peek_ofs, copy);
+                    dst += copy;
+                }
 
                 rem -= copy;
-                dst += copy;
                 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);
+                    This->peek_len = This->peek_ofs = 0;
+
+            } 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 (dst) {
+                    if(src)
+                        memcpy(dst, src, copy);
+                    else
+                        silence_buffer(This->ss.format, dst, copy);
+
+                    dst += copy;
+                }
 
-                dst += copy;
                 rem -= copy;
 
                 if (copy < src_len) {
@@ -1030,7 +1002,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;
                 }
@@ -1039,56 +1015,100 @@ 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)
+static DWORD WINAPI pulse_timer_cb(void *user)
 {
-    while (bytes >= This->capture_period) {
-        size_t src_len, copy, rem = This->capture_period;
-        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)
-                copy = src_len;
-
-            src_len -= copy;
-            rem -= copy;
-
-            if (!src_len) {
-                This->peek_ofs = 0;
-                pa_stream_drop(This->stream);
-            } else
-                This->peek_ofs += copy;
-            bytes -= copy;
+    DWORD delay;
+    UINT32 adv_bytes;
+    ACImpl *This = user;
+    int success;
+    pa_operation *o;
+
+    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;
+
+        o = pa_stream_update_timing_info(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);
         }
-    }
-}
+        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_underran){
+                    This->last_time = now;
+                    This->just_started = TRUE;
+                }
 
-static void pulse_rd_callback(pa_stream *s, size_t bytes, void *userdata)
-{
-    ACImpl *This = userdata;
+                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;
 
-    TRACE("Readable total: %zu, fragsize: %u\n", bytes, pa_stream_get_buffer_attr(s)->fragsize);
-    assert(bytes >= This->peek_ofs);
-    bytes -= This->peek_ofs;
-    if (bytes < This->capture_period)
-        return;
+                    adv_usec = now - This->last_time;
 
-    if (This->started)
-        pulse_rd_loop(This, bytes);
-    else
-        pulse_rd_drop(This, bytes);
+                    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->lcl_offs_bytes %= This->real_bufsize_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);
+
+        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);
+    }
 
-    if (This->event)
-        SetEvent(This->event);
+    return 0;
 }
 
 static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) {
@@ -1117,15 +1137,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_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_ADJUST_LATENCY);
     if (ret < 0) {
         WARN("Returns %i\n", ret);
         return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
@@ -1136,11 +1157,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;
 }
 
@@ -1275,6 +1294,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);
@@ -1570,7 +1594,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));
@@ -1617,38 +1641,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];
+    period = pulse_def_period[This->dataflow == eCapture];
+    if (duration < 3 * period)
+        duration = 3 * period;
 
-        /* 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;
+    This->period_bytes = pa_frame_size(&This->ss) * MulDiv(period, This->ss.rate, 10000000);
 
-        /* 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);
-
-    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);
@@ -1656,39 +1661,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);
             }
         }
     }
@@ -1753,12 +1753,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;
@@ -1766,7 +1766,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)
@@ -1778,7 +1778,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,
@@ -2024,6 +2024,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) {
@@ -2038,8 +2040,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;
@@ -2111,7 +2115,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)
@@ -2119,14 +2123,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;
@@ -2292,10 +2296,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);
 
@@ -2314,37 +2317,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);
@@ -2356,12 +2341,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);
     }
@@ -2372,88 +2358,47 @@ 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 (flags & AUDCLNT_BUFFERFLAGS_SILENT) wr_flags |= WINEPULSE_WRITE_SILENT;
-        if (!This->locked_ptr) wr_flags |= WINEPULSE_WRITE_NOFREE;
-
-        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)
+        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)
+        silence_buffer(This->ss.format, buffer, written_bytes);
+
+    if(This->locked < 0)
+        pulse_wrap_buffer(This, buffer, written_bytes);
+
+    This->held_bytes += written_bytes;
+    This->pa_held_bytes += written_bytes;
+    if(This->pa_held_bytes > This->real_bufsize_bytes){
+        This->pa_offs_bytes += This->pa_held_bytes - This->real_bufsize_bytes;
+        This->pa_offs_bytes %= This->real_bufsize_bytes;
+        This->pa_held_bytes = This->real_bufsize_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;
 }
 
@@ -2530,13 +2475,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);
         }
@@ -2570,11 +2515,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;
@@ -2594,7 +2539,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);
@@ -2686,7 +2631,7 @@ static HRESULT WINAPI AudioClock_GetPosition(IAudioClock *iface, UINT64 *pos,
         return hr;
     }
 
-    *pos = This->clock_written;
+    *pos = This->clock_written - This->held_bytes;
 
     if (This->share == AUDCLNT_SHAREMODE_EXCLUSIVE)
         *pos /= pa_frame_size(&This->ss);
-- 
2.28.0




More information about the wine-devel mailing list