[PATCH v2 2/6] winealsa: Move timer_loop to the unixlib.

Huw Davies huw at codeweavers.com
Wed Mar 2 04:53:16 CST 2022


Signed-off-by: Huw Davies <huw at codeweavers.com>
---
 dlls/winealsa.drv/alsa.c     | 240 +++++++++++++++++++++++++++++++++++
 dlls/winealsa.drv/mmdevdrv.c | 229 +++------------------------------
 dlls/winealsa.drv/unixlib.h  |   9 +-
 3 files changed, 268 insertions(+), 210 deletions(-)

diff --git a/dlls/winealsa.drv/alsa.c b/dlls/winealsa.drv/alsa.c
index 73971c0f8bb..d455a3787e8 100644
--- a/dlls/winealsa.drv/alsa.c
+++ b/dlls/winealsa.drv/alsa.c
@@ -960,6 +960,12 @@ static NTSTATUS release_stream(void *args)
     struct alsa_stream *stream = params->stream;
     SIZE_T size;
 
+    if(params->timer_thread){
+        stream->please_quit = TRUE;
+        NtWaitForSingleObject(params->timer_thread, FALSE, NULL);
+        NtClose(params->timer_thread);
+    }
+
     snd_pcm_drop(stream->pcm_handle);
     snd_pcm_close(stream->pcm_handle);
     if(stream->local_buffer){
@@ -1215,6 +1221,239 @@ static NTSTATUS write_best_effort(void *args)
     return STATUS_SUCCESS;
 }
 
+static snd_pcm_sframes_t alsa_write_buffer_wrap(struct alsa_stream *stream, BYTE *buf,
+        snd_pcm_uframes_t buflen, snd_pcm_uframes_t offs,
+        snd_pcm_uframes_t to_write)
+{
+    snd_pcm_sframes_t ret = 0;
+
+    while(to_write){
+        snd_pcm_uframes_t chunk;
+        snd_pcm_sframes_t tmp;
+
+        if(offs + to_write > buflen)
+            chunk = buflen - offs;
+        else
+            chunk = to_write;
+
+        tmp = alsa_write_best_effort(stream, buf + offs * stream->fmt->nBlockAlign, chunk);
+        if(tmp < 0)
+            return ret;
+        if(!tmp)
+            break;
+
+        ret += tmp;
+        to_write -= tmp;
+        offs += tmp;
+        offs %= buflen;
+    }
+
+    return ret;
+}
+
+static UINT buf_ptr_diff(UINT left, UINT right, UINT bufsize)
+{
+    if(left <= right)
+        return right - left;
+    return bufsize - (left - right);
+}
+
+static UINT data_not_in_alsa(struct alsa_stream *stream)
+{
+    UINT32 diff;
+
+    diff = buf_ptr_diff(stream->lcl_offs_frames, stream->wri_offs_frames, stream->bufsize_frames);
+    if(diff)
+        return diff;
+
+    return stream->held_frames - stream->data_in_alsa_frames;
+}
+
+/* Here's the buffer setup:
+ *
+ *  vvvvvvvv sent to HW already
+ *          vvvvvvvv in ALSA buffer but rewindable
+ * [dddddddddddddddd] ALSA buffer
+ *         [dddddddddddddddd--------] mmdevapi buffer
+ *          ^^^^^^^^ data_in_alsa_frames
+ *          ^^^^^^^^^^^^^^^^ held_frames
+ *                  ^ lcl_offs_frames
+ *                          ^ wri_offs_frames
+ *
+ * GetCurrentPadding is held_frames
+ *
+ * During period callback, we decrement held_frames, fill ALSA buffer, and move
+ *   lcl_offs forward
+ *
+ * During Stop, we rewind the ALSA buffer
+ */
+static void alsa_write_data(struct alsa_stream *stream)
+{
+    snd_pcm_sframes_t written;
+    snd_pcm_uframes_t avail, max_copy_frames, data_frames_played;
+    int err;
+
+    /* this call seems to be required to get an accurate snd_pcm_state() */
+    avail = snd_pcm_avail_update(stream->pcm_handle);
+
+    if(snd_pcm_state(stream->pcm_handle) == SND_PCM_STATE_XRUN){
+        TRACE("XRun state, recovering\n");
+
+        avail = stream->alsa_bufsize_frames;
+
+        if((err = snd_pcm_recover(stream->pcm_handle, -EPIPE, 1)) < 0)
+            WARN("snd_pcm_recover failed: %d (%s)\n", err, snd_strerror(err));
+
+        if((err = snd_pcm_reset(stream->pcm_handle)) < 0)
+            WARN("snd_pcm_reset failed: %d (%s)\n", err, snd_strerror(err));
+
+        if((err = snd_pcm_prepare(stream->pcm_handle)) < 0)
+            WARN("snd_pcm_prepare failed: %d (%s)\n", err, snd_strerror(err));
+    }
+
+    TRACE("avail: %ld\n", avail);
+
+    /* Add a lead-in when starting with too few frames to ensure
+     * continuous rendering.  Additional benefit: Force ALSA to start. */
+    if(stream->data_in_alsa_frames == 0 && stream->held_frames < stream->alsa_period_frames)
+    {
+        alsa_write_best_effort(stream, stream->silence_buf,
+                               stream->alsa_period_frames - stream->held_frames);
+        stream->vol_adjusted_frames = 0;
+    }
+
+    if(stream->started)
+        max_copy_frames = data_not_in_alsa(stream);
+    else
+        max_copy_frames = 0;
+
+    data_frames_played = min(stream->data_in_alsa_frames, avail);
+    stream->data_in_alsa_frames -= data_frames_played;
+
+    if(stream->held_frames > data_frames_played){
+        if(stream->started)
+            stream->held_frames -= data_frames_played;
+    }else
+        stream->held_frames = 0;
+
+    while(avail && max_copy_frames){
+        snd_pcm_uframes_t to_write;
+
+        to_write = min(avail, max_copy_frames);
+
+        written = alsa_write_buffer_wrap(stream, stream->local_buffer,
+                stream->bufsize_frames, stream->lcl_offs_frames, to_write);
+        if(written <= 0)
+            break;
+
+        avail -= written;
+        stream->lcl_offs_frames += written;
+        stream->lcl_offs_frames %= stream->bufsize_frames;
+        stream->data_in_alsa_frames += written;
+        max_copy_frames -= written;
+    }
+
+    if(stream->event)
+        NtSetEvent(stream->event, NULL);
+}
+
+static void alsa_read_data(struct alsa_stream *stream)
+{
+    snd_pcm_sframes_t nread;
+    UINT32 pos = stream->wri_offs_frames, limit = stream->held_frames;
+    unsigned int i;
+
+    if(!stream->started)
+        goto exit;
+
+    /* FIXME: Detect overrun and signal DATA_DISCONTINUITY
+     * How to count overrun frames and report them as position increase? */
+    limit = stream->bufsize_frames - max(limit, pos);
+
+    nread = snd_pcm_readi(stream->pcm_handle,
+            stream->local_buffer + pos * stream->fmt->nBlockAlign, limit);
+    TRACE("read %ld from %u limit %u\n", nread, pos, limit);
+    if(nread < 0){
+        int ret;
+
+        if(nread == -EAGAIN) /* no data yet */
+            return;
+
+        WARN("read failed, recovering: %ld (%s)\n", nread, snd_strerror(nread));
+
+        ret = snd_pcm_recover(stream->pcm_handle, nread, 0);
+        if(ret < 0){
+            WARN("Recover failed: %d (%s)\n", ret, snd_strerror(ret));
+            return;
+        }
+
+        nread = snd_pcm_readi(stream->pcm_handle,
+                stream->local_buffer + pos * stream->fmt->nBlockAlign, limit);
+        if(nread < 0){
+            WARN("read failed: %ld (%s)\n", nread, snd_strerror(nread));
+            return;
+        }
+    }
+
+    for(i = 0; i < stream->fmt->nChannels; i++)
+        if(stream->vols[i] != 0.0f)
+            break;
+    if(i == stream->fmt->nChannels){ /* mute */
+        int err;
+        if((err = snd_pcm_format_set_silence(stream->alsa_format,
+                        stream->local_buffer + pos * stream->fmt->nBlockAlign,
+                        nread)) < 0)
+            WARN("Setting buffer to silence failed: %d (%s)\n", err,
+                    snd_strerror(err));
+    }
+
+    stream->wri_offs_frames += nread;
+    stream->wri_offs_frames %= stream->bufsize_frames;
+    stream->held_frames += nread;
+
+exit:
+    if(stream->event)
+        NtSetEvent(stream->event, NULL);
+}
+
+static NTSTATUS timer_loop(void *args)
+{
+    struct timer_loop_params *params = args;
+    struct alsa_stream *stream = params->stream;
+    LARGE_INTEGER delay, next;
+    int adjust;
+
+    alsa_lock(stream);
+
+    delay.QuadPart = -stream->mmdev_period_rt;
+    NtQueryPerformanceCounter(&stream->last_period_time, NULL);
+    next.QuadPart = stream->last_period_time.QuadPart + stream->mmdev_period_rt;
+
+    while(!stream->please_quit){
+        if(stream->flow == eRender)
+            alsa_write_data(stream);
+        else if(stream->flow == eCapture)
+            alsa_read_data(stream);
+        alsa_unlock(stream);
+
+        NtDelayExecution(FALSE, &delay);
+
+        alsa_lock(stream);
+        NtQueryPerformanceCounter(&stream->last_period_time, NULL);
+        adjust = next.QuadPart - stream->last_period_time.QuadPart;
+        if(adjust > stream->mmdev_period_rt / 2)
+            adjust = stream->mmdev_period_rt / 2;
+        else if(adjust < -stream->mmdev_period_rt / 2)
+            adjust = -stream->mmdev_period_rt / 2;
+        delay.QuadPart = -(stream->mmdev_period_rt + adjust);
+        next.QuadPart += stream->mmdev_period_rt;
+    }
+
+    alsa_unlock(stream);
+
+    return STATUS_SUCCESS;
+}
+
 static NTSTATUS is_format_supported(void *args)
 {
     struct is_format_supported_params *params = args;
@@ -1522,6 +1761,7 @@ unixlib_entry_t __wine_unix_call_funcs[] =
     get_endpoint_ids,
     create_stream,
     release_stream,
+    timer_loop,
     is_format_supported,
     get_mix_format,
     get_buffer_size,
diff --git a/dlls/winealsa.drv/mmdevdrv.c b/dlls/winealsa.drv/mmdevdrv.c
index c06b08709f2..2748cc552a6 100644
--- a/dlls/winealsa.drv/mmdevdrv.c
+++ b/dlls/winealsa.drv/mmdevdrv.c
@@ -109,7 +109,7 @@ struct ACImpl {
     UINT32 channel_count;
     struct alsa_stream *stream;
 
-    HANDLE timer;
+    HANDLE timer_thread;
 
     AudioSession *session;
     AudioSessionWrapper *session_wrapper;
@@ -128,8 +128,6 @@ typedef struct _SessionMgr {
     IMMDevice *device;
 } SessionMgr;
 
-static HANDLE g_timer_q;
-
 static CRITICAL_SECTION g_sessions_lock;
 static CRITICAL_SECTION_DEBUG g_sessions_lock_debug =
 {
@@ -216,9 +214,6 @@ BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, void *reserved)
         if(NtQueryVirtualMemory(GetCurrentProcess(), dll, MemoryWineUnixFuncs,
                                 &alsa_handle, sizeof(alsa_handle), NULL))
             return FALSE;
-        g_timer_q = CreateTimerQueue();
-        if(!g_timer_q)
-            return FALSE;
         break;
 
     case DLL_PROCESS_DETACH:
@@ -252,17 +247,30 @@ static void alsa_unlock(struct alsa_stream *stream)
     pthread_mutex_unlock(&stream->lock);
 }
 
-static HRESULT alsa_stream_release(struct alsa_stream *stream)
+static HRESULT alsa_stream_release(struct alsa_stream *stream, HANDLE timer_thread)
 {
     struct release_stream_params params;
 
     params.stream = stream;
+    params.timer_thread = timer_thread;
 
     ALSA_CALL(release_stream, &params);
 
     return params.result;
 }
 
+static DWORD WINAPI alsa_timer_thread(void *user)
+{
+    struct alsa_stream *stream = user;
+    struct timer_loop_params params;
+
+    params.stream = stream;
+
+    ALSA_CALL(timer_loop, &params);
+
+    return 0;
+}
+
 static void set_device_guid(EDataFlow flow, HKEY drv_key, const WCHAR *key_name,
         GUID *guid)
 {
@@ -552,17 +560,6 @@ static ULONG WINAPI AudioClient_Release(IAudioClient3 *iface)
     ref = InterlockedDecrement(&This->ref);
     TRACE("(%p) Refcount now %u\n", This, ref);
     if(!ref){
-        if(This->timer){
-            HANDLE event;
-            DWORD wait;
-            event = CreateEventW(NULL, TRUE, FALSE, NULL);
-            wait = !DeleteTimerQueueTimer(g_timer_q, This->timer, event);
-            wait = wait && GetLastError() == ERROR_IO_PENDING;
-            if(event && wait)
-                WaitForSingleObject(event, INFINITE);
-            CloseHandle(event);
-        }
-
         IAudioClient3_Stop(iface);
         IMMDevice_Release(This->parent);
         IUnknown_Release(This->pUnkFTMarshal);
@@ -573,7 +570,7 @@ static ULONG WINAPI AudioClient_Release(IAudioClient3 *iface)
         }
         HeapFree(GetProcessHeap(), 0, This->vols);
         if (This->stream)
-            alsa_stream_release(This->stream);
+            alsa_stream_release(This->stream, This->timer_thread);
         HeapFree(GetProcessHeap(), 0, This);
     }
     return ref;
@@ -807,7 +804,7 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface,
 
 exit:
     if(FAILED(params.result)){
-        alsa_stream_release(stream);
+        alsa_stream_release(stream, NULL);
         HeapFree(GetProcessHeap(), 0, This->vols);
         This->vols = NULL;
     }else{
@@ -1011,187 +1008,6 @@ static snd_pcm_sframes_t alsa_write_buffer_wrap(struct alsa_stream *stream, BYTE
     return ret;
 }
 
-static UINT buf_ptr_diff(UINT left, UINT right, UINT bufsize)
-{
-    if(left <= right)
-        return right - left;
-    return bufsize - (left - right);
-}
-
-static UINT data_not_in_alsa(struct alsa_stream *stream)
-{
-    UINT32 diff;
-
-    diff = buf_ptr_diff(stream->lcl_offs_frames, stream->wri_offs_frames, stream->bufsize_frames);
-    if(diff)
-        return diff;
-
-    return stream->held_frames - stream->data_in_alsa_frames;
-}
-/* Here's the buffer setup:
- *
- *  vvvvvvvv sent to HW already
- *          vvvvvvvv in ALSA buffer but rewindable
- * [dddddddddddddddd] ALSA buffer
- *         [dddddddddddddddd--------] mmdevapi buffer
- *          ^^^^^^^^ data_in_alsa_frames
- *          ^^^^^^^^^^^^^^^^ held_frames
- *                  ^ lcl_offs_frames
- *                          ^ wri_offs_frames
- *
- * GetCurrentPadding is held_frames
- *
- * During period callback, we decrement held_frames, fill ALSA buffer, and move
- *   lcl_offs forward
- *
- * During Stop, we rewind the ALSA buffer
- */
-static void alsa_write_data(struct alsa_stream *stream)
-{
-    snd_pcm_sframes_t written;
-    snd_pcm_uframes_t avail, max_copy_frames, data_frames_played;
-    int err;
-
-    /* this call seems to be required to get an accurate snd_pcm_state() */
-    avail = snd_pcm_avail_update(stream->pcm_handle);
-
-    if(snd_pcm_state(stream->pcm_handle) == SND_PCM_STATE_XRUN){
-        TRACE("XRun state, recovering\n");
-
-        avail = stream->alsa_bufsize_frames;
-
-        if((err = snd_pcm_recover(stream->pcm_handle, -EPIPE, 1)) < 0)
-            WARN("snd_pcm_recover failed: %d (%s)\n", err, snd_strerror(err));
-
-        if((err = snd_pcm_reset(stream->pcm_handle)) < 0)
-            WARN("snd_pcm_reset failed: %d (%s)\n", err, snd_strerror(err));
-
-        if((err = snd_pcm_prepare(stream->pcm_handle)) < 0)
-            WARN("snd_pcm_prepare failed: %d (%s)\n", err, snd_strerror(err));
-    }
-
-    TRACE("avail: %ld\n", avail);
-
-    /* Add a lead-in when starting with too few frames to ensure
-     * continuous rendering.  Additional benefit: Force ALSA to start. */
-    if(stream->data_in_alsa_frames == 0 && stream->held_frames < stream->alsa_period_frames)
-    {
-        alsa_write_best_effort(stream, stream->silence_buf,
-                               stream->alsa_period_frames - stream->held_frames);
-        stream->vol_adjusted_frames = 0;
-    }
-
-    if(stream->started)
-        max_copy_frames = data_not_in_alsa(stream);
-    else
-        max_copy_frames = 0;
-
-    data_frames_played = min(stream->data_in_alsa_frames, avail);
-    stream->data_in_alsa_frames -= data_frames_played;
-
-    if(stream->held_frames > data_frames_played){
-        if(stream->started)
-            stream->held_frames -= data_frames_played;
-    }else
-        stream->held_frames = 0;
-
-    while(avail && max_copy_frames){
-        snd_pcm_uframes_t to_write;
-
-        to_write = min(avail, max_copy_frames);
-
-        written = alsa_write_buffer_wrap(stream, stream->local_buffer,
-                stream->bufsize_frames, stream->lcl_offs_frames, to_write);
-        if(written <= 0)
-            break;
-
-        avail -= written;
-        stream->lcl_offs_frames += written;
-        stream->lcl_offs_frames %= stream->bufsize_frames;
-        stream->data_in_alsa_frames += written;
-        max_copy_frames -= written;
-    }
-
-    if(stream->event)
-        SetEvent(stream->event);
-}
-
-static void alsa_read_data(struct alsa_stream *stream)
-{
-    snd_pcm_sframes_t nread;
-    UINT32 pos = stream->wri_offs_frames, limit = stream->held_frames;
-    unsigned int i;
-
-    if(!stream->started)
-        goto exit;
-
-    /* FIXME: Detect overrun and signal DATA_DISCONTINUITY
-     * How to count overrun frames and report them as position increase? */
-    limit = stream->bufsize_frames - max(limit, pos);
-
-    nread = snd_pcm_readi(stream->pcm_handle,
-            stream->local_buffer + pos * stream->fmt->nBlockAlign, limit);
-    TRACE("read %ld from %u limit %u\n", nread, pos, limit);
-    if(nread < 0){
-        int ret;
-
-        if(nread == -EAGAIN) /* no data yet */
-            return;
-
-        WARN("read failed, recovering: %ld (%s)\n", nread, snd_strerror(nread));
-
-        ret = snd_pcm_recover(stream->pcm_handle, nread, 0);
-        if(ret < 0){
-            WARN("Recover failed: %d (%s)\n", ret, snd_strerror(ret));
-            return;
-        }
-
-        nread = snd_pcm_readi(stream->pcm_handle,
-                stream->local_buffer + pos * stream->fmt->nBlockAlign, limit);
-        if(nread < 0){
-            WARN("read failed: %ld (%s)\n", nread, snd_strerror(nread));
-            return;
-        }
-    }
-
-    for(i = 0; i < stream->fmt->nChannels; i++)
-        if(stream->vols[i] != 0.0f)
-            break;
-    if(i == stream->fmt->nChannels){ /* mute */
-        int err;
-        if((err = snd_pcm_format_set_silence(stream->alsa_format,
-                        stream->local_buffer + pos * stream->fmt->nBlockAlign,
-                        nread)) < 0)
-            WARN("Setting buffer to silence failed: %d (%s)\n", err,
-                    snd_strerror(err));
-    }
-
-    stream->wri_offs_frames += nread;
-    stream->wri_offs_frames %= stream->bufsize_frames;
-    stream->held_frames += nread;
-
-exit:
-    if(stream->event)
-        SetEvent(stream->event);
-}
-
-static void CALLBACK alsa_push_buffer_data(void *user, BOOLEAN timer)
-{
-    ACImpl *This = user;
-    struct alsa_stream *stream = This->stream;
-
-    alsa_lock(stream);
-
-    QueryPerformanceCounter(&stream->last_period_time);
-
-    if(stream->flow == eRender)
-        alsa_write_data(stream);
-    else if(stream->flow == eCapture)
-        alsa_read_data(stream);
-
-    alsa_unlock(stream);
-}
-
 static snd_pcm_uframes_t interp_elapsed_frames(struct alsa_stream *stream)
 {
     LARGE_INTEGER time_freq, current_time, time_diff;
@@ -1291,14 +1107,9 @@ static HRESULT WINAPI AudioClient_Start(IAudioClient3 *iface)
         }
     }
 
-    if(!This->timer){
-        if(!CreateTimerQueueTimer(&This->timer, g_timer_q, alsa_push_buffer_data,
-                This, 0, stream->mmdev_period_rt / 10000, WT_EXECUTEINTIMERTHREAD)){
-            alsa_unlock(stream);
-            LeaveCriticalSection(&g_sessions_lock);
-            WARN("Unable to create timer: %u\n", GetLastError());
-            return E_OUTOFMEMORY;
-        }
+    if(!This->timer_thread){
+        This->timer_thread = CreateThread(NULL, 0, alsa_timer_thread, This->stream, 0, NULL);
+        SetThreadPriority(This->timer_thread, THREAD_PRIORITY_TIME_CRITICAL);
     }
 
     stream->started = TRUE;
diff --git a/dlls/winealsa.drv/unixlib.h b/dlls/winealsa.drv/unixlib.h
index 3fd6d2a73ee..4acbbcbcf11 100644
--- a/dlls/winealsa.drv/unixlib.h
+++ b/dlls/winealsa.drv/unixlib.h
@@ -37,7 +37,7 @@ struct alsa_stream
     int alsa_channels;
     int alsa_channel_map[32];
 
-    BOOL started;
+    BOOL started, please_quit;
     REFERENCE_TIME mmdev_period_rt;
     UINT64 written_frames, last_pos_frames;
     UINT32 bufsize_frames, held_frames, tmp_buffer_frames, mmdev_period_frames;
@@ -87,9 +87,15 @@ struct create_stream_params
 struct release_stream_params
 {
     struct alsa_stream *stream;
+    HANDLE timer_thread;
     HRESULT result;
 };
 
+struct timer_loop_params
+{
+    struct alsa_stream *stream;
+};
+
 struct is_format_supported_params
 {
     const char *alsa_name;
@@ -142,6 +148,7 @@ enum alsa_funcs
     alsa_get_endpoint_ids,
     alsa_create_stream,
     alsa_release_stream,
+    alsa_timer_loop,
     alsa_is_format_supported,
     alsa_get_mix_format,
     alsa_get_buffer_size,
-- 
2.25.1




More information about the wine-devel mailing list