Signed-off-by: Huw Davies huw@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, ¶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,
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@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, ¶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);
} return ref;alsa_stream_release(This->stream, This->timer_thread); HeapFree(GetProcessHeap(), 0, This);
@@ -807,7 +804,7 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface,
exit: if(FAILED(params.result)){
alsa_stream_release(stream);
}else{alsa_stream_release(stream, NULL); HeapFree(GetProcessHeap(), 0, This->vols); This->vols = NULL;
@@ -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
On Tue, Mar 01, 2022 at 10:48:30AM -0600, Andrew Eikum wrote:
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):
[snip]
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?
Hi Andrew,
Thanks for this looking at this. I'd somehow convinced myself that we didn't need to account for drift, but apparently we do.
I've sent in v2 of the series that adds "adjust" logic similar to that in the pulse driver.
Huw.