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:
parent
107d95165a
commit
7bced2878a
4 changed files with 277 additions and 1 deletions
|
@ -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
175
dlls/sapi/async.c
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue