Signed-off-by: Huw Davies <huw(a)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,
--
2.25.1