From: Shaun Ren sren@codeweavers.com
Also introduce async helpers.
The buffers cannot be freed directly in wave_out_proc, because calling waveOut related functions in the callback could cause a deadlock. --- dlls/sapi/Makefile.in | 1 + dlls/sapi/async.c | 172 +++++++++++++++++++++++++++++++++++++++ dlls/sapi/mmaudio.c | 78 +++++++++++++++++- dlls/sapi/sapi_private.h | 24 ++++++ 4 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 dlls/sapi/async.c
diff --git a/dlls/sapi/Makefile.in b/dlls/sapi/Makefile.in index 8a1ead0b78b..4229320e473 100644 --- a/dlls/sapi/Makefile.in +++ b/dlls/sapi/Makefile.in @@ -3,6 +3,7 @@ IMPORTS = uuid ole32 user32 advapi32 DELAYIMPORTS = winmm
C_SRCS = \ + async.c \ automation.c \ main.c \ mmaudio.c \ diff --git a/dlls/sapi/async.c b/dlls/sapi/async.c new file mode 100644 index 00000000000..c0fd716c234 --- /dev/null +++ b/dlls/sapi/async.c @@ -0,0 +1,172 @@ +/* + * Speech API (SAPI) async helper implementation. + * + * Copyright 2023 Shaun Ren for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <stdarg.h> + +#include "windef.h" +#include "winbase.h" +#include "objbase.h" + +#include "wine/heap.h" +#include "wine/list.h" +#include "wine/debug.h" + +#include "sapi_private.h" + +WINE_DEFAULT_DEBUG_CHANNEL(sapi); + +static struct async_task *async_dequeue_task(struct async_queue *queue) +{ + struct async_task *task; + + EnterCriticalSection(&queue->cs); + task = LIST_ENTRY(list_head(&queue->tasks), struct async_task, entry); + if (task) list_remove(&task->entry); + LeaveCriticalSection(&queue->cs); + + return task; +} + +void async_empty_queue(struct async_queue *queue) +{ + struct list *ptr; + + EnterCriticalSection(&queue->cs); + while ((ptr = list_head(&queue->tasks))) + { + struct async_task *task = LIST_ENTRY(ptr, struct async_task, entry); + list_remove(&task->entry); + heap_free(task); + } + LeaveCriticalSection(&queue->cs); + + SetEvent(queue->empty); +} + +static void CALLBACK async_worker(TP_CALLBACK_INSTANCE *instance, void *ctx) +{ + struct async_queue *queue = ctx; + HANDLE handles[2] = { queue->cancel, queue->wait }; + DWORD ret; + + SetEvent(queue->ready); + + for (;;) + { + ret = WaitForMultipleObjects(2, handles, FALSE, INFINITE); + if (ret == WAIT_OBJECT_0) + goto cancel; + else if (ret == WAIT_OBJECT_0 + 1) + { + struct async_task *task; + + while ((task = async_dequeue_task(queue))) + { + ResetEvent(queue->empty); + task->proc(task); + heap_free(task); + if (WaitForSingleObject(queue->cancel, 0) == WAIT_OBJECT_0) + goto cancel; + } + + SetEvent(queue->empty); + } + else + ERR("WaitForMultipleObjects failed: %#lx.\n", ret); + } + +cancel: + async_empty_queue(queue); + TRACE("cancelled.\n"); + SetEvent(queue->ready); +} + +HRESULT async_start_queue(struct async_queue *queue) +{ + HRESULT hr; + + if (queue->init) + return S_OK; + + InitializeCriticalSection(&queue->cs); + list_init(&queue->tasks); + + if (!(queue->wait = CreateEventW(NULL, FALSE, FALSE, NULL)) || + !(queue->ready = CreateEventW(NULL, FALSE, FALSE, NULL)) || + !(queue->cancel = CreateEventW(NULL, FALSE, FALSE, NULL)) || + !(queue->empty = CreateEventW(NULL, TRUE, TRUE, NULL))) + goto fail; + + queue->init = TRUE; + + if (!TrySubmitThreadpoolCallback(async_worker, queue, NULL)) + goto fail; + + WaitForSingleObject(queue->ready, INFINITE); + return S_OK; + +fail: + hr = HRESULT_FROM_WIN32(GetLastError()); + DeleteCriticalSection(&queue->cs); + if (queue->wait) CloseHandle(queue->wait); + if (queue->ready) CloseHandle(queue->ready); + if (queue->cancel) CloseHandle(queue->cancel); + if (queue->empty) CloseHandle(queue->empty); + memset(queue, 0, sizeof(*queue)); + return hr; +} + +void async_cancel_queue(struct async_queue *queue) +{ + if (!queue->init) return; + + SetEvent(queue->cancel); + WaitForSingleObject(queue->ready, INFINITE); + + DeleteCriticalSection(&queue->cs); + CloseHandle(queue->wait); + CloseHandle(queue->ready); + CloseHandle(queue->cancel); + CloseHandle(queue->empty); + + memset(queue, 0, sizeof(*queue)); +} + +HRESULT async_queue_task(struct async_queue *queue, struct async_task *task) +{ + HRESULT hr; + + if (FAILED(hr = async_start_queue(queue))) + return hr; + + EnterCriticalSection(&queue->cs); + list_add_tail(&queue->tasks, &task->entry); + LeaveCriticalSection(&queue->cs); + + SetEvent(queue->wait); + + return S_OK; +} + +void async_wait_queue_empty(struct async_queue *queue, DWORD timeout) +{ + if (!queue->init) return; + WaitForSingleObject(queue->empty, timeout); +} diff --git a/dlls/sapi/mmaudio.c b/dlls/sapi/mmaudio.c index 20e6cc5064f..add670ff3dc 100644 --- a/dlls/sapi/mmaudio.c +++ b/dlls/sapi/mmaudio.c @@ -60,7 +60,12 @@ struct mmaudio HWAVEIN in; HWAVEOUT out; } hwave; + HANDLE event; + struct async_queue queue; CRITICAL_SECTION cs; + + size_t pending_buf_count; + CRITICAL_SECTION pending_cs; };
static inline struct mmaudio *impl_from_ISpEventSource(ISpEventSource *iface) @@ -371,8 +376,13 @@ static ULONG WINAPI mmsysaudio_Release(ISpMMSysAudio *iface) { ISpMMSysAudio_SetState(iface, SPAS_CLOSED, 0);
+ async_wait_queue_empty(&This->queue, INFINITE); + async_cancel_queue(&This->queue); + if (This->token) ISpObjectToken_Release(This->token); heap_free(This->wfx); + CloseHandle(This->event); + DeleteCriticalSection(&This->pending_cs); DeleteCriticalSection(&This->cs);
heap_free(This); @@ -487,6 +497,57 @@ static HRESULT WINAPI mmsysaudio_GetFormat(ISpMMSysAudio *iface, GUID *format, W return S_OK; }
+struct free_buf_task +{ + struct async_task task; + struct mmaudio *audio; + WAVEHDR *buf; +}; + +static void free_out_buf_proc(struct async_task *task) +{ + struct free_buf_task *fbt = (struct free_buf_task *)task; + size_t buf_count; + + TRACE("(%p).\n", task); + + waveOutUnprepareHeader(fbt->audio->hwave.out, fbt->buf, sizeof(WAVEHDR)); + heap_free(fbt->buf); + + EnterCriticalSection(&fbt->audio->pending_cs); + buf_count = --fbt->audio->pending_buf_count; + LeaveCriticalSection(&fbt->audio->pending_cs); + if (!buf_count) + SetEvent(fbt->audio->event); + TRACE("pending_buf_count = %Iu.\n", buf_count); +} + +static void CALLBACK wave_out_proc(HWAVEOUT hwo, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) +{ + struct mmaudio *This = (struct mmaudio *)instance; + struct free_buf_task *task; + + TRACE("(%p, %#x, %08Ix, %08Ix, %08Ix).\n", hwo, msg, instance, param1, param2); + + switch (msg) + { + case WOM_DONE: + if (!(task = heap_alloc(sizeof(*task)))) + { + ERR("failed to allocate free_buf_task.\n"); + break; + } + task->task.proc = free_out_buf_proc; + task->audio = This; + task->buf = (WAVEHDR *)param1; + async_queue_task(&This->queue, (struct async_task *)task); + break; + + default: + break; + } +} + static HRESULT WINAPI mmsysaudio_SetState(ISpMMSysAudio *iface, SPAUDIOSTATE state, ULONGLONG reserved) { struct mmaudio *This = impl_from_ISpMMSysAudio(iface); @@ -507,7 +568,14 @@ static HRESULT WINAPI mmsysaudio_SetState(ISpMMSysAudio *iface, SPAUDIOSTATE sta
if (This->state == SPAS_CLOSED) { - if (waveOutOpen(&This->hwave.out, This->device_id, This->wfx, 0, 0, 0) != MMSYSERR_NOERROR) + if (FAILED(hr = async_start_queue(&This->queue))) + { + ERR("Failed to start async queue: %#lx.\n", hr); + goto done; + } + + if (waveOutOpen(&This->hwave.out, This->device_id, This->wfx, (DWORD_PTR)wave_out_proc, + (DWORD_PTR)This, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) { hr = SPERR_GENERIC_MMSYS_ERROR; goto done; @@ -516,6 +584,10 @@ static HRESULT WINAPI mmsysaudio_SetState(ISpMMSysAudio *iface, SPAUDIOSTATE sta
if (state == SPAS_CLOSED && This->state != SPAS_CLOSED) { + waveOutReset(This->hwave.out); + /* Wait until all buffers are freed. */ + WaitForSingleObject(This->event, INFINITE); + if (waveOutClose(This->hwave.out) != MMSYSERR_NOERROR) { hr = SPERR_GENERIC_MMSYS_ERROR; @@ -776,7 +848,11 @@ static HRESULT mmaudio_create(IUnknown *outer, REFIID iid, void **obj, enum flow This->wfx->wBitsPerSample = 16; This->wfx->cbSize = 0;
+ This->pending_buf_count = 0; + This->event = CreateEventW(NULL, TRUE, TRUE, NULL); + InitializeCriticalSection(&This->cs); + InitializeCriticalSection(&This->pending_cs);
hr = ISpMMSysAudio_QueryInterface(&This->ISpMMSysAudio_iface, iid, obj); ISpMMSysAudio_Release(&This->ISpMMSysAudio_iface); diff --git a/dlls/sapi/sapi_private.h b/dlls/sapi/sapi_private.h index 88b1a27516f..d0cd7669623 100644 --- a/dlls/sapi/sapi_private.h +++ b/dlls/sapi/sapi_private.h @@ -19,6 +19,30 @@ */
#include "wine/heap.h" +#include "wine/list.h" + +struct async_task +{ + struct list entry; + void (*proc)(struct async_task *); +}; + +struct async_queue +{ + BOOL init; + HANDLE wait; + HANDLE ready; + HANDLE empty; + HANDLE cancel; + struct list tasks; + CRITICAL_SECTION cs; +}; + +HRESULT async_start_queue(struct async_queue *queue); +void async_empty_queue(struct async_queue *queue); +void async_cancel_queue(struct async_queue *queue); +HRESULT async_queue_task(struct async_queue *queue, struct async_task *task); +void async_wait_queue_empty(struct async_queue *queue, DWORD timeout);
HRESULT data_key_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; HRESULT file_stream_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN;