sapi: Free completed buffers asynchronously in SpMMAudio.

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.
This commit is contained in:
Shaun Ren 2023-05-30 23:29:22 -04:00 committed by Alexandre Julliard
parent 107d95165a
commit 7bced2878a
4 changed files with 277 additions and 1 deletions

View file

@ -3,6 +3,7 @@ IMPORTS = uuid ole32 user32 advapi32
DELAYIMPORTS = winmm
C_SRCS = \
async.c \
automation.c \
main.c \
mmaudio.c \

175
dlls/sapi/async.c Normal file
View file

@ -0,0 +1,175 @@
/*
* 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 = NULL;
struct list *head;
EnterCriticalSection(&queue->cs);
if ((head = list_head(&queue->tasks)))
{
task = LIST_ENTRY(head, struct async_task, entry);
list_remove(head);
}
LeaveCriticalSection(&queue->cs);
return task;
}
void async_empty_queue(struct async_queue *queue)
{
struct async_task *task, *next;
EnterCriticalSection(&queue->cs);
LIST_FOR_EACH_ENTRY_SAFE(task, next, &queue->tasks, 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);
}

View file

@ -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);

View file

@ -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;