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

Andrew Eikum aeikum at codeweavers.com
Tue Mar 1 10:48:30 CST 2022


This one causes a ton of underruns with native xaudio2. I tested with
the "James Cameron's Avatar The Game Demo". I can provide you the
installation files, if you can't dig them up someplace.

It looks to me like the new thread timer slowly drifts, which means
the device eventually runs out of data and we never catch up (note
drifting thousandths and accumulating avail value):

    8252.559:0174:trace:alsa:alsa_write_data avail: 3712
    ...
    8252.619:0174:trace:alsa:alsa_write_data avail: 3616
    8252.630:0174:trace:alsa:alsa_write_data avail: 3664
    ...
    8252.720:0174:trace:alsa:alsa_write_data avail: 3712
    8252.731:0174:trace:alsa:alsa_write_data avail: 3712
    ...
    8252.841:0174:trace:alsa:alsa_write_data avail: 3808
    8252.852:0174:trace:alsa:alsa_write_data avail: 3856
    ...
    8252.972:0174:trace:alsa:alsa_write_data avail: 4000
    8252.983:0174:trace:alsa:alsa_write_data avail: 4000

While the old timer-queue based method didn't drift (note consistent
thousandths):

    8187.912:0140:trace:alsa:alsa_write_data avail: 1088
    ...
    8188.072:0140:trace:alsa:alsa_write_data avail: 1088
    8188.083:0140:trace:alsa:alsa_write_data avail: 1136
    ...
    8188.212:0140:trace:alsa:alsa_write_data avail: 1280
    ...
    8188.352:0140:trace:alsa:alsa_write_data avail: 1232
    8188.362:0140:trace:alsa:alsa_write_data avail: 1280
    ...
    8188.403:0140:trace:alsa:alsa_write_data avail: 1136
    8188.412:0140:trace:alsa:alsa_write_data avail: 1184

The pulse driver doesn't run into this problem because it keeps track
of the time between callbacks and accounts for the drift, see the
'adjust' parameter and 'delay' calculation in
pulse.c:pulse_timer_loop.

We could do the same adjustment in ALSA, or find a way to keep using
the timer queue?

Andrew

On Fri, Feb 25, 2022 at 09:32:20AM +0000, Huw Davies wrote:
> Signed-off-by: Huw Davies <huw at codeweavers.com>
> ---
>  dlls/winealsa.drv/alsa.c     | 230 +++++++++++++++++++++++++++++++++++
>  dlls/winealsa.drv/mmdevdrv.c | 229 +++-------------------------------
>  dlls/winealsa.drv/unixlib.h  |   9 +-
>  3 files changed, 258 insertions(+), 210 deletions(-)
> 
> diff --git a/dlls/winealsa.drv/alsa.c b/dlls/winealsa.drv/alsa.c
> index 73971c0f8bb..91ead000497 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,229 @@ 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;
> +
> +    alsa_lock(stream);
> +
> +    delay.QuadPart = -stream->mmdev_period_rt;
> +
> +    while(!stream->please_quit){
> +        NtQueryPerformanceCounter(&stream->last_period_time, NULL);
> +
> +        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);
> +    }
> +
> +    alsa_unlock(stream);
> +
> +    return STATUS_SUCCESS;
> +}
> +
>  static NTSTATUS is_format_supported(void *args)
>  {
>      struct is_format_supported_params *params = args;
> @@ -1522,6 +1751,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