[PATCH v2 2/6] winealsa: Move timer_loop to the unixlib.
Andrew Eikum
aeikum at codeweavers.com
Thu Mar 3 12:56:26 CST 2022
Signed-off-by: Andrew Eikum <aeikum at codeweavers.com>
On Wed, Mar 02, 2022 at 10:53:16AM +0000, Huw Davies wrote:
> 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, ¶ms);
>
> 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, ¶ms);
> +
> + 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