This MR allows for the use of sapi-based TTS engines, such as Amazon Polly, in Far Cry 6 and possibly other applications.
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/tests/tts.c | 314 ++++++++++++++++++++++++++++++++++++++++++ dlls/sapi/tts.c | 80 ++++++++++- 2 files changed, 391 insertions(+), 3 deletions(-)
diff --git a/dlls/sapi/tests/tts.c b/dlls/sapi/tests/tts.c index ffe2e5f73a7..c17e4f5b7c3 100644 --- a/dlls/sapi/tests/tts.c +++ b/dlls/sapi/tests/tts.c @@ -98,16 +98,266 @@ static void test_interfaces(void) ISpeechVoice_Release(speech_voice); }
+#define TESTENGINE_CLSID L"{57C7E6B1-2FC2-4E8E-B968-1410A39E7198}" +static const GUID CLSID_TestEngine = {0x57C7E6B1,0x2FC2,0x4E8E,{0xB9,0x68,0x14,0x10,0xA3,0x9E,0x71,0x98}}; + +struct test_engine +{ + ISpTTSEngine ISpTTSEngine_iface; + ISpObjectWithToken ISpObjectWithToken_iface; + + ISpObjectToken *token; + + BOOL speak_called; + DWORD flags; + GUID fmtid; + SPVTEXTFRAG *frag_list; +}; + +static void copy_frag_list(const SPVTEXTFRAG *frag_list, SPVTEXTFRAG **ret_frag_list) +{ + SPVTEXTFRAG *frag, *prev = NULL; + + if (!frag_list) + { + *ret_frag_list = NULL; + return; + } + + while (frag_list) + { + frag = malloc(sizeof(*frag) + frag_list->ulTextLen * sizeof(WCHAR)); + memcpy(frag, frag_list, sizeof(*frag)); + + if (frag_list->pTextStart) + { + frag->pTextStart = (WCHAR *)(frag + 1); + memcpy(frag + 1, frag_list->pTextStart, frag->ulTextLen * sizeof(WCHAR)); + } + + frag->pNext = NULL; + + if (prev) + prev->pNext = frag; + else + *ret_frag_list = frag; + + prev = frag; + frag_list = frag_list->pNext; + } +} + +static void reset_engine_params(struct test_engine *engine) +{ + SPVTEXTFRAG *frag, *next; + + engine->speak_called = FALSE; + engine->flags = 0xdeadbeef; + memset(&engine->fmtid, 0xde, sizeof(engine->fmtid)); + + for (frag = engine->frag_list; frag; frag = next) + { + next = frag->pNext; + free(frag); + } + engine->frag_list = NULL; +} + +static inline struct test_engine *impl_from_ISpTTSEngine(ISpTTSEngine *iface) +{ + return CONTAINING_RECORD(iface, struct test_engine, ISpTTSEngine_iface); +} + +static inline struct test_engine *impl_from_ISpObjectWithToken(ISpObjectWithToken *iface) +{ + return CONTAINING_RECORD(iface, struct test_engine, ISpObjectWithToken_iface); +} + +static HRESULT WINAPI test_engine_QueryInterface(ISpTTSEngine *iface, REFIID iid, void **obj) +{ + struct test_engine *engine = impl_from_ISpTTSEngine(iface); + + if (IsEqualIID(iid, &IID_IUnknown) || IsEqualIID(iid, &IID_ISpTTSEngine)) + *obj = &engine->ISpTTSEngine_iface; + else if (IsEqualIID(iid, &IID_ISpObjectWithToken)) + *obj = &engine->ISpObjectWithToken_iface; + else + { + *obj = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown *)*obj); + return S_OK; +} + +static ULONG WINAPI test_engine_AddRef(ISpTTSEngine *iface) +{ + return 2; +} + +static ULONG WINAPI test_engine_Release(ISpTTSEngine *iface) +{ + return 1; +} + +static HRESULT WINAPI test_engine_Speak(ISpTTSEngine *iface, DWORD flags, REFGUID fmtid, + const WAVEFORMATEX *wfx, const SPVTEXTFRAG *frag_list, + ISpTTSEngineSite *site) +{ + struct test_engine *engine = impl_from_ISpTTSEngine(iface); + + engine->flags = flags; + engine->fmtid = *fmtid; + copy_frag_list(frag_list, &engine->frag_list); + engine->speak_called = TRUE; + + return S_OK; +} + +static HRESULT WINAPI test_engine_GetOutputFormat(ISpTTSEngine *iface, const GUID *fmtid, + const WAVEFORMATEX *wfx, GUID *out_fmtid, + WAVEFORMATEX **out_wfx) +{ + *out_fmtid = SPDFID_WaveFormatEx; + *out_wfx = CoTaskMemAlloc(sizeof(WAVEFORMATEX)); + (*out_wfx)->wFormatTag = WAVE_FORMAT_PCM; + (*out_wfx)->nChannels = 1; + (*out_wfx)->nSamplesPerSec = 22050; + (*out_wfx)->wBitsPerSample = 16; + (*out_wfx)->nBlockAlign = 2; + (*out_wfx)->nAvgBytesPerSec = 22050 * 2; + (*out_wfx)->cbSize = 0; + + return S_OK; +} + +static ISpTTSEngineVtbl test_engine_vtbl = +{ + test_engine_QueryInterface, + test_engine_AddRef, + test_engine_Release, + test_engine_Speak, + test_engine_GetOutputFormat, +}; + +static HRESULT WINAPI objwithtoken_QueryInterface(ISpObjectWithToken *iface, REFIID iid, void **obj) +{ + struct test_engine *engine = impl_from_ISpObjectWithToken(iface); + + return ISpTTSEngine_QueryInterface(&engine->ISpTTSEngine_iface, iid, obj); +} + +static ULONG WINAPI objwithtoken_AddRef(ISpObjectWithToken *iface) +{ + return 2; +} + +static ULONG WINAPI objwithtoken_Release(ISpObjectWithToken *iface) +{ + return 1; +} + +static HRESULT WINAPI objwithtoken_SetObjectToken(ISpObjectWithToken *iface, ISpObjectToken *token) +{ + struct test_engine *engine = impl_from_ISpObjectWithToken(iface); + + if (!token) + return E_INVALIDARG; + + ISpObjectToken_AddRef(token); + engine->token = token; + + return S_OK; +} + +static HRESULT WINAPI objwithtoken_GetObjectToken(ISpObjectWithToken *iface, ISpObjectToken **token) +{ + struct test_engine *engine = impl_from_ISpObjectWithToken(iface); + + *token = engine->token; + if (*token) + ISpObjectToken_AddRef(*token); + + return S_OK; +} + +static const ISpObjectWithTokenVtbl objwithtoken_vtbl = +{ + objwithtoken_QueryInterface, + objwithtoken_AddRef, + objwithtoken_Release, + objwithtoken_SetObjectToken, + objwithtoken_GetObjectToken +}; + +static struct test_engine test_engine = {{&test_engine_vtbl}, {&objwithtoken_vtbl}}; + +static HRESULT WINAPI ClassFactory_QueryInterface(IClassFactory *iface, REFIID riid, void **ppv) +{ + if (IsEqualGUID(&IID_IUnknown, riid) || IsEqualGUID(&IID_IClassFactory, riid)) + { + *ppv = iface; + return S_OK; + } + + *ppv = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI ClassFactory_AddRef(IClassFactory *iface) +{ + return 2; +} + +static ULONG WINAPI ClassFactory_Release(IClassFactory *iface) +{ + return 1; +} + +static HRESULT WINAPI ClassFactory_CreateInstance(IClassFactory *iface, + IUnknown *pUnkOuter, REFIID riid, void **ppv) +{ + ok(pUnkOuter == NULL, "pUnkOuter != NULL.\n"); + ok(IsEqualGUID(riid, &IID_IUnknown) || IsEqualGUID(riid, &IID_ISpTTSEngine), + "riid = %s.\n", wine_dbgstr_guid(riid)); + + *ppv = &test_engine.ISpTTSEngine_iface; + return S_OK; +} + +static HRESULT WINAPI ClassFactory_LockServer(IClassFactory *iface, BOOL fLock) +{ + ok(0, "unexpected call.\n"); + return E_NOTIMPL; +} + +static const IClassFactoryVtbl ClassFactoryVtbl = { + ClassFactory_QueryInterface, + ClassFactory_AddRef, + ClassFactory_Release, + ClassFactory_CreateInstance, + ClassFactory_LockServer +}; + +static IClassFactory test_engine_cf = { &ClassFactoryVtbl }; + static void test_spvoice(void) { + static const WCHAR test_token_id[] = L"HKEY_LOCAL_MACHINE\Software\Wine\Winetest\sapi\tts\TestEngine"; + static const WCHAR test_text[] = L"Hello! This is a test sentence."; + ISpVoice *voice; ISpMMSysAudio *audio_out; ISpObjectTokenCategory *token_cat; ISpObjectToken *token; WCHAR *token_id = NULL, *default_token_id = NULL; + ISpDataKey *attrs_key; LONG rate; USHORT volume; ULONG stream_num; + DWORD regid; + DWORD start, duration; HRESULT hr;
if (waveOutGetNumDevs() == 0) { @@ -210,15 +460,79 @@ static void test_spvoice(void) hr = ISpVoice_SetVolume(voice, 101); ok(hr == E_INVALIDARG, "got %#lx.\n", hr);
+ hr = CoRegisterClassObject(&CLSID_TestEngine, (IUnknown *)&test_engine_cf, + CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, ®id); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = CoCreateInstance(&CLSID_SpObjectToken, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectToken, (void **)&token); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpObjectToken_SetId(token, NULL, test_token_id, TRUE); + ok(hr == S_OK || broken(hr == E_ACCESSDENIED) /* w1064_adm */, "got %#lx.\n", hr); + if (hr == E_ACCESSDENIED) + { + win_skip("token SetId access denied.\n"); + goto done; + } + + ISpObjectToken_SetStringValue(token, L"CLSID", TESTENGINE_CLSID); + hr = ISpObjectToken_CreateKey(token, L"Attributes", &attrs_key); + ok(hr == S_OK, "got %#lx.\n", hr); + ISpDataKey_SetStringValue(attrs_key, L"Language", L"409"); + ISpDataKey_Release(attrs_key); + + hr = ISpVoice_SetVoice(voice, token); + ok(hr == S_OK, "got %#lx.\n", hr); + + test_engine.speak_called = FALSE; hr = ISpVoice_Speak(voice, NULL, SPF_PURGEBEFORESPEAK, NULL); ok(hr == S_OK, "got %#lx.\n", hr); + ok(!test_engine.speak_called, "ISpTTSEngine::Speak was called.\n");
stream_num = 0xdeadbeef; hr = ISpVoice_Speak(voice, NULL, SPF_PURGEBEFORESPEAK, &stream_num); ok(hr == S_OK, "got %#lx.\n", hr); ok(stream_num == 0xdeadbeef, "got %lu.\n", stream_num);
+ test_engine.speak_called = FALSE; + stream_num = 0xdeadbeef; + start = GetTickCount(); + hr = ISpVoice_Speak(voice, test_text, SPF_DEFAULT, &stream_num); + duration = GetTickCount() - start; + ok(hr == S_OK, "got %#lx.\n", hr); + ok(test_engine.speak_called, "ISpTTSEngine::Speak was not called.\n"); + ok(test_engine.flags == SPF_DEFAULT, "got %#lx.\n", test_engine.flags); + ok(test_engine.frag_list != NULL, "frag_list is NULL.\n"); + ok(test_engine.frag_list->pNext == NULL, "frag_list->pNext != NULL.\n"); + ok(test_engine.frag_list->ulTextLen == wcslen(test_text), "got %lu.\n", test_engine.frag_list->ulTextLen); + ok(!wcsncmp(test_text, test_engine.frag_list->pTextStart, wcslen(test_text)), + "got %s.\n", wine_dbgstr_w(test_engine.frag_list->pTextStart)); + ok(stream_num == 1, "got %lu.\n", stream_num); + ok(duration < 500, "took %lu ms.\n", duration); + + reset_engine_params(&test_engine); + stream_num = 0xdeadbeef; + start = GetTickCount(); + hr = ISpVoice_Speak(voice, test_text, SPF_DEFAULT | SPF_ASYNC | SPF_NLP_SPEAK_PUNC, &stream_num); + duration = GetTickCount() - start; + ok(hr == S_OK, "got %#lx.\n", hr); + todo_wine ok(stream_num == 1, "got %lu.\n", stream_num); + ok(duration < 500, "took %lu ms.\n", duration); + + Sleep(200); + ok(test_engine.speak_called, "ISpTTSEngine::Speak was not called.\n"); + ok(test_engine.flags == SPF_NLP_SPEAK_PUNC, "got %#lx.\n", test_engine.flags); + ok(test_engine.frag_list != NULL, "frag_list is NULL.\n"); + ok(test_engine.frag_list->pNext == NULL, "frag_list->pNext != NULL.\n"); + ok(test_engine.frag_list->ulTextLen == wcslen(test_text), "got %lu.\n", test_engine.frag_list->ulTextLen); + ok(!wcsncmp(test_text, test_engine.frag_list->pTextStart, wcslen(test_text)), + "got %s.\n", wine_dbgstr_w(test_engine.frag_list->pTextStart)); + +done: + reset_engine_params(&test_engine); ISpVoice_Release(voice); + ISpObjectToken_Release(token); ISpMMSysAudio_Release(audio_out); }
diff --git a/dlls/sapi/tts.c b/dlls/sapi/tts.c index 7ec5aa059d9..9a022aca73c 100644 --- a/dlls/sapi/tts.c +++ b/dlls/sapi/tts.c @@ -731,15 +731,89 @@ struct speak_task DWORD flags; };
+static HRESULT set_output_format(ISpStreamFormat *output, ISpTTSEngine *engine, GUID *fmtid, WAVEFORMATEX **wfx) +{ + GUID output_fmtid; + WAVEFORMATEX *output_wfx = NULL; + ISpAudio *audio = NULL; + HRESULT hr; + + if (FAILED(hr = ISpStreamFormat_GetFormat(output, &output_fmtid, &output_wfx))) + return hr; + if (FAILED(hr = ISpTTSEngine_GetOutputFormat(engine, &output_fmtid, output_wfx, fmtid, wfx))) + goto done; + if (!IsEqualGUID(fmtid, &SPDFID_WaveFormatEx)) + { + hr = E_INVALIDARG; + goto done; + } + + if (memcmp(output_wfx, *wfx, sizeof(WAVEFORMATEX)) || + memcmp(output_wfx + 1, *wfx + 1, output_wfx->cbSize)) + { + if (FAILED(hr = ISpStreamFormat_QueryInterface(output, &IID_ISpAudio, (void **)&audio)) || + FAILED(hr = ISpAudio_SetFormat(audio, &SPDFID_WaveFormatEx, *wfx))) + goto done; + } + +done: + CoTaskMemFree(output_wfx); + if (audio) ISpAudio_Release(audio); + return hr; +} + static void speak_proc(struct async_task *task) { struct speak_task *speak_task = (struct speak_task *)task; + struct speech_voice *This = speak_task->voice; + GUID fmtid; + WAVEFORMATEX *wfx = NULL; + ISpTTSEngine *engine = NULL; + ISpAudio *audio = NULL; + HRESULT hr; + + TRACE("(%p).\n", task);
- FIXME("(%p): stub.\n", task); + EnterCriticalSection(&This->cs); + + if (FAILED(hr = set_output_format(This->output, This->engine, &fmtid, &wfx))) + { + LeaveCriticalSection(&This->cs); + ERR("failed setting output format: %#lx.\n", hr); + goto done; + } + engine = This->engine; + ISpTTSEngine_AddRef(engine); + + if (SUCCEEDED(ISpStreamFormat_QueryInterface(This->output, &IID_ISpAudio, (void **)&audio))) + ISpAudio_SetState(audio, SPAS_RUN, 0); + + LeaveCriticalSection(&This->cs); + + hr = ISpTTSEngine_Speak(engine, speak_task->flags, &fmtid, wfx, speak_task->frag_list, speak_task->site); + if (SUCCEEDED(hr)) + { + ISpStreamFormat_Commit(This->output, STGC_DEFAULT); + if (audio) + WaitForSingleObject(ISpAudio_EventHandle(audio), INFINITE); + } + else + WARN("ISpTTSEngine_Speak failed: %#lx.\n", hr); + +done: + if (audio) + { + ISpAudio_SetState(audio, SPAS_CLOSED, 0); + ISpAudio_Release(audio); + } + CoTaskMemFree(wfx); + if (engine) ISpTTSEngine_Release(engine); + heap_free(speak_task->frag_list); + ISpTTSEngineSite_Release(speak_task->site);
if (speak_task->result) { - speak_task->result->hr = E_NOTIMPL; + speak_task->result->hr = hr; SetEvent(speak_task->result->done); } } @@ -757,7 +831,7 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR ULONG stream_num; HRESULT hr;
- FIXME("(%p, %p, %#lx, %p): semi-stub.\n", iface, contents, flags, stream_num_out); + TRACE("(%p, %p, %#lx, %p).\n", iface, contents, flags, stream_num_out);
flags &= ~SPF_IS_NOT_XML; if (flags & ~(SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_NLP_SPEAK_PUNC))
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/tests/tts.c | 14 +++++++++++++- dlls/sapi/tts.c | 10 ++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/dlls/sapi/tests/tts.c b/dlls/sapi/tests/tts.c index c17e4f5b7c3..d2b98e6fbfb 100644 --- a/dlls/sapi/tests/tts.c +++ b/dlls/sapi/tests/tts.c @@ -206,12 +206,24 @@ static HRESULT WINAPI test_engine_Speak(ISpTTSEngine *iface, DWORD flags, REFGUI ISpTTSEngineSite *site) { struct test_engine *engine = impl_from_ISpTTSEngine(iface); + char *buf; + int i; + HRESULT hr;
engine->flags = flags; engine->fmtid = *fmtid; copy_frag_list(frag_list, &engine->frag_list); engine->speak_called = TRUE;
+ buf = calloc(1, 22050 * 2 / 5); + for (i = 0; i < 5; i++) + { + hr = ISpTTSEngineSite_Write(site, buf, 22050 * 2 / 5, NULL); + ok(hr == S_OK, "got %#lx.\n", hr); + Sleep(100); + } + free(buf); + return S_OK; }
@@ -509,7 +521,7 @@ static void test_spvoice(void) ok(!wcsncmp(test_text, test_engine.frag_list->pTextStart, wcslen(test_text)), "got %s.\n", wine_dbgstr_w(test_engine.frag_list->pTextStart)); ok(stream_num == 1, "got %lu.\n", stream_num); - ok(duration < 500, "took %lu ms.\n", duration); + ok(duration > 800 && duration < 3000, "took %lu ms.\n", duration);
reset_engine_params(&test_engine); stream_num = 0xdeadbeef; diff --git a/dlls/sapi/tts.c b/dlls/sapi/tts.c index 9a022aca73c..acbef907646 100644 --- a/dlls/sapi/tts.c +++ b/dlls/sapi/tts.c @@ -27,6 +27,7 @@ #include "objbase.h"
#include "sapiddk.h" +#include "sperror.h"
#include "wine/debug.h"
@@ -1211,9 +1212,14 @@ static DWORD WINAPI ttsenginesite_GetActions(ISpTTSEngineSite *iface)
static HRESULT WINAPI ttsenginesite_Write(ISpTTSEngineSite *iface, const void *buf, ULONG cb, ULONG *cb_written) { - FIXME("(%p, %p, %ld, %p): stub.\n", iface, buf, cb, cb_written); + struct tts_engine_site *This = impl_from_ISpTTSEngineSite(iface);
- return E_NOTIMPL; + TRACE("(%p, %p, %ld, %p).\n", iface, buf, cb, cb_written); + + if (!This->voice->output) + return SPERR_UNINITIALIZED; + + return ISpStreamFormat_Write(This->voice->output, buf, cb, cb_written); }
static HRESULT WINAPI ttsenginesite_GetRate(ISpTTSEngineSite *iface, LONG *rate)
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/tests/tts.c | 44 +++++++++++++++++++++++++++++++++++-- dlls/sapi/tts.c | 51 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 87 insertions(+), 8 deletions(-)
diff --git a/dlls/sapi/tests/tts.c b/dlls/sapi/tests/tts.c index d2b98e6fbfb..86a9312a37b 100644 --- a/dlls/sapi/tests/tts.c +++ b/dlls/sapi/tests/tts.c @@ -112,6 +112,8 @@ struct test_engine DWORD flags; GUID fmtid; SPVTEXTFRAG *frag_list; + LONG rate; + USHORT volume; };
static void copy_frag_list(const SPVTEXTFRAG *frag_list, SPVTEXTFRAG **ret_frag_list) @@ -154,6 +156,8 @@ static void reset_engine_params(struct test_engine *engine) engine->speak_called = FALSE; engine->flags = 0xdeadbeef; memset(&engine->fmtid, 0xde, sizeof(engine->fmtid)); + engine->rate = 0xdeadbeef; + engine->volume = 0xbeef;
for (frag = engine->frag_list; frag; frag = next) { @@ -206,6 +210,7 @@ static HRESULT WINAPI test_engine_Speak(ISpTTSEngine *iface, DWORD flags, REFGUI ISpTTSEngineSite *site) { struct test_engine *engine = impl_from_ISpTTSEngine(iface); + DWORD actions; char *buf; int i; HRESULT hr; @@ -215,11 +220,26 @@ static HRESULT WINAPI test_engine_Speak(ISpTTSEngine *iface, DWORD flags, REFGUI copy_frag_list(frag_list, &engine->frag_list); engine->speak_called = TRUE;
+ actions = ISpTTSEngineSite_GetActions(site); + ok(actions == (SPVES_CONTINUE | SPVES_RATE | SPVES_VOLUME), "got %#lx.\n", actions); + + hr = ISpTTSEngineSite_GetRate(site, &engine->rate); + ok(hr == S_OK, "got %#lx.\n", hr); + actions = ISpTTSEngineSite_GetActions(site); + ok(actions == (SPVES_CONTINUE | SPVES_VOLUME), "got %#lx.\n", actions); + + hr = ISpTTSEngineSite_GetVolume(site, &engine->volume); + ok(hr == S_OK, "got %#lx.\n", hr); + actions = ISpTTSEngineSite_GetActions(site); + ok(actions == SPVES_CONTINUE, "got %#lx.\n", actions); + buf = calloc(1, 22050 * 2 / 5); for (i = 0; i < 5; i++) { + if (ISpTTSEngineSite_GetActions(site) & SPVES_ABORT) + break; hr = ISpTTSEngineSite_Write(site, buf, 22050 * 2 / 5, NULL); - ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK || hr == SP_AUDIO_STOPPED, "got %#lx.\n", hr); Sleep(100); } free(buf); @@ -507,7 +527,10 @@ static void test_spvoice(void) ok(hr == S_OK, "got %#lx.\n", hr); ok(stream_num == 0xdeadbeef, "got %lu.\n", stream_num);
- test_engine.speak_called = FALSE; + ISpVoice_SetRate(voice, 0); + ISpVoice_SetVolume(voice, 100); + + reset_engine_params(&test_engine); stream_num = 0xdeadbeef; start = GetTickCount(); hr = ISpVoice_Speak(voice, test_text, SPF_DEFAULT, &stream_num); @@ -520,6 +543,8 @@ static void test_spvoice(void) ok(test_engine.frag_list->ulTextLen == wcslen(test_text), "got %lu.\n", test_engine.frag_list->ulTextLen); ok(!wcsncmp(test_text, test_engine.frag_list->pTextStart, wcslen(test_text)), "got %s.\n", wine_dbgstr_w(test_engine.frag_list->pTextStart)); + ok(test_engine.rate == 0, "got %ld.\n", test_engine.rate); + ok(test_engine.volume == 100, "got %d.\n", test_engine.volume); ok(stream_num == 1, "got %lu.\n", stream_num); ok(duration > 800 && duration < 3000, "took %lu ms.\n", duration);
@@ -540,6 +565,21 @@ static void test_spvoice(void) ok(test_engine.frag_list->ulTextLen == wcslen(test_text), "got %lu.\n", test_engine.frag_list->ulTextLen); ok(!wcsncmp(test_text, test_engine.frag_list->pTextStart, wcslen(test_text)), "got %s.\n", wine_dbgstr_w(test_engine.frag_list->pTextStart)); + ok(test_engine.rate == 0, "got %ld.\n", test_engine.rate); + ok(test_engine.volume == 100, "got %d.\n", test_engine.volume); + + Sleep(2000); + + reset_engine_params(&test_engine); + hr = ISpVoice_Speak(voice, test_text, SPF_DEFAULT | SPF_ASYNC, NULL); + ok(hr == S_OK, "got %#lx.\n", hr); + + Sleep(200); + start = GetTickCount(); + hr = ISpVoice_Speak(voice, NULL, SPF_PURGEBEFORESPEAK, NULL); + duration = GetTickCount() - start; + ok(hr == S_OK, "got %#lx.\n", hr); + ok(duration < 300, "took %lu ms.\n", duration);
done: reset_engine_params(&test_engine); diff --git a/dlls/sapi/tts.c b/dlls/sapi/tts.c index acbef907646..432a021ea1c 100644 --- a/dlls/sapi/tts.c +++ b/dlls/sapi/tts.c @@ -45,6 +45,7 @@ struct speech_voice ISpStreamFormat *output; ISpTTSEngine *engine; LONG cur_stream_num; + DWORD actions; USHORT volume; LONG rate; struct async_queue queue; @@ -777,6 +778,13 @@ static void speak_proc(struct async_task *task)
EnterCriticalSection(&This->cs);
+ if (This->actions & SPVES_ABORT) + { + LeaveCriticalSection(&This->cs); + hr = S_OK; + goto done; + } + if (FAILED(hr = set_output_format(This->output, This->engine, &fmtid, &wfx))) { LeaveCriticalSection(&This->cs); @@ -789,6 +797,8 @@ static void speak_proc(struct async_task *task) if (SUCCEEDED(ISpStreamFormat_QueryInterface(This->output, &IID_ISpAudio, (void **)&audio))) ISpAudio_SetState(audio, SPAS_RUN, 0);
+ This->actions = SPVES_RATE | SPVES_VOLUME; + LeaveCriticalSection(&This->cs);
hr = ISpTTSEngine_Speak(engine, speak_task->flags, &fmtid, wfx, speak_task->frag_list, speak_task->site); @@ -847,6 +857,7 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR
EnterCriticalSection(&This->cs);
+ This->actions = SPVES_ABORT; if (This->output && SUCCEEDED(ISpStreamFormat_QueryInterface(This->output, &IID_ISpAudio, (void **)&audio))) { ISpAudio_SetState(audio, SPAS_CLOSED, 0); @@ -857,6 +868,10 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR
async_empty_queue(&This->queue);
+ EnterCriticalSection(&This->cs); + This->actions = SPVES_CONTINUE; + LeaveCriticalSection(&This->cs); + if (!contents || !*contents) return S_OK; } @@ -1007,6 +1022,7 @@ static HRESULT WINAPI spvoice_SetRate(ISpVoice *iface, LONG rate)
EnterCriticalSection(&This->cs); This->rate = rate; + This->actions |= SPVES_RATE; LeaveCriticalSection(&This->cs);
return S_OK; @@ -1036,6 +1052,7 @@ static HRESULT WINAPI spvoice_SetVolume(ISpVoice *iface, USHORT volume)
EnterCriticalSection(&This->cs); This->volume = volume; + This->actions |= SPVES_VOLUME; LeaveCriticalSection(&This->cs);
return S_OK; @@ -1205,9 +1222,16 @@ static HRESULT WINAPI ttsenginesite_GetEventInterest(ISpTTSEngineSite *iface, UL
static DWORD WINAPI ttsenginesite_GetActions(ISpTTSEngineSite *iface) { - FIXME("(%p): stub.\n", iface); + struct tts_engine_site *This = impl_from_ISpTTSEngineSite(iface); + DWORD actions; + + TRACE("(%p).\n", iface); + + EnterCriticalSection(&This->voice->cs); + actions = This->voice->actions; + LeaveCriticalSection(&This->voice->cs);
- return SPVES_CONTINUE; + return actions; }
static HRESULT WINAPI ttsenginesite_Write(ISpTTSEngineSite *iface, const void *buf, ULONG cb, ULONG *cb_written) @@ -1224,16 +1248,30 @@ static HRESULT WINAPI ttsenginesite_Write(ISpTTSEngineSite *iface, const void *b
static HRESULT WINAPI ttsenginesite_GetRate(ISpTTSEngineSite *iface, LONG *rate) { - FIXME("(%p, %p): stub.\n", iface, rate); + struct tts_engine_site *This = impl_from_ISpTTSEngineSite(iface);
- return E_NOTIMPL; + TRACE("(%p, %p).\n", iface, rate); + + EnterCriticalSection(&This->voice->cs); + *rate = This->voice->rate; + This->voice->actions &= ~SPVES_RATE; + LeaveCriticalSection(&This->voice->cs); + + return S_OK; }
static HRESULT WINAPI ttsenginesite_GetVolume(ISpTTSEngineSite *iface, USHORT *volume) { - FIXME("(%p, %p): stub.\n", iface, volume); + struct tts_engine_site *This = impl_from_ISpTTSEngineSite(iface);
- return E_NOTIMPL; + TRACE("(%p, %p).\n", iface, volume); + + EnterCriticalSection(&This->voice->cs); + *volume = This->voice->volume; + This->voice->actions &= ~SPVES_VOLUME; + LeaveCriticalSection(&This->voice->cs); + + return S_OK; }
static HRESULT WINAPI ttsenginesite_GetSkipInfo(ISpTTSEngineSite *iface, SPVSKIPTYPE *type, LONG *skip_count) @@ -1351,6 +1389,7 @@ HRESULT speech_voice_create(IUnknown *outer, REFIID iid, void **obj) This->output = NULL; This->engine = NULL; This->cur_stream_num = 0; + This->actions = SPVES_CONTINUE; This->volume = 100; This->rate = 0; memset(&This->queue, 0, sizeof(This->queue));
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/async.c | 6 +++--- dlls/sapi/sapi_private.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/dlls/sapi/async.c b/dlls/sapi/async.c index 491ca657c1a..b02778ae7ba 100644 --- a/dlls/sapi/async.c +++ b/dlls/sapi/async.c @@ -171,8 +171,8 @@ HRESULT async_queue_task(struct async_queue *queue, struct async_task *task) return S_OK; }
-void async_wait_queue_empty(struct async_queue *queue, DWORD timeout) +HRESULT async_wait_queue_empty(struct async_queue *queue, DWORD timeout) { - if (!queue->init) return; - WaitForSingleObject(queue->empty, timeout); + if (!queue->init) return WAIT_OBJECT_0; + return WaitForSingleObject(queue->empty, timeout); } diff --git a/dlls/sapi/sapi_private.h b/dlls/sapi/sapi_private.h index 94046926b2e..3cdb59b36a8 100644 --- a/dlls/sapi/sapi_private.h +++ b/dlls/sapi/sapi_private.h @@ -42,7 +42,7 @@ 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 async_wait_queue_empty(struct async_queue *queue, DWORD timeout);
HRESULT data_key_create( IUnknown *outer, REFIID iid, void **obj ); HRESULT file_stream_create( IUnknown *outer, REFIID iid, void **obj );
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/tests/tts.c | 17 ++++++++++++++--- dlls/sapi/tts.c | 11 +++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/dlls/sapi/tests/tts.c b/dlls/sapi/tests/tts.c index 86a9312a37b..39f62ac551e 100644 --- a/dlls/sapi/tests/tts.c +++ b/dlls/sapi/tests/tts.c @@ -548,6 +548,12 @@ static void test_spvoice(void) ok(stream_num == 1, "got %lu.\n", stream_num); ok(duration > 800 && duration < 3000, "took %lu ms.\n", duration);
+ start = GetTickCount(); + hr = ISpVoice_WaitUntilDone(voice, INFINITE); + duration = GetTickCount() - start; + ok(hr == S_OK, "got %#lx.\n", hr); + ok(duration < 200, "took %lu ms.\n", duration); + reset_engine_params(&test_engine); stream_num = 0xdeadbeef; start = GetTickCount(); @@ -557,7 +563,14 @@ static void test_spvoice(void) todo_wine ok(stream_num == 1, "got %lu.\n", stream_num); ok(duration < 500, "took %lu ms.\n", duration);
- Sleep(200); + hr = ISpVoice_WaitUntilDone(voice, 100); + ok(hr == S_FALSE, "got %#lx.\n", hr); + + hr = ISpVoice_WaitUntilDone(voice, INFINITE); + duration = GetTickCount() - start; + ok(hr == S_OK, "got %#lx.\n", hr); + ok(duration > 800 && duration < 3000, "took %lu ms.\n", duration); + ok(test_engine.speak_called, "ISpTTSEngine::Speak was not called.\n"); ok(test_engine.flags == SPF_NLP_SPEAK_PUNC, "got %#lx.\n", test_engine.flags); ok(test_engine.frag_list != NULL, "frag_list is NULL.\n"); @@ -568,8 +581,6 @@ static void test_spvoice(void) ok(test_engine.rate == 0, "got %ld.\n", test_engine.rate); ok(test_engine.volume == 100, "got %d.\n", test_engine.volume);
- Sleep(2000); - reset_engine_params(&test_engine); hr = ISpVoice_Speak(voice, test_text, SPF_DEFAULT | SPF_ASYNC, NULL); ok(hr == S_OK, "got %#lx.\n", hr); diff --git a/dlls/sapi/tts.c b/dlls/sapi/tts.c index 432a021ea1c..4763ef9f324 100644 --- a/dlls/sapi/tts.c +++ b/dlls/sapi/tts.c @@ -1073,9 +1073,16 @@ static HRESULT WINAPI spvoice_GetVolume(ISpVoice *iface, USHORT *volume)
static HRESULT WINAPI spvoice_WaitUntilDone(ISpVoice *iface, ULONG timeout) { - FIXME("(%p, %ld): stub.\n", iface, timeout); + struct speech_voice *This = impl_from_ISpVoice(iface); + HRESULT hr;
- return E_NOTIMPL; + TRACE("(%p, %ld).\n", iface, timeout); + + hr = async_wait_queue_empty(&This->queue, timeout); + + if (hr == WAIT_OBJECT_0) return S_OK; + else if (hr == WAIT_TIMEOUT) return S_FALSE; + return hr; }
static HRESULT WINAPI spvoice_SetSyncSpeakTimeout(ISpVoice *iface, ULONG timeout)
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=135045
Your paranoid android.
=== build (build log) ===
error: patch failed: dlls/sapi/tests/tts.c:98 error: patch failed: dlls/sapi/tts.c:731 error: patch failed: dlls/sapi/tests/tts.c:206 error: patch failed: dlls/sapi/tts.c:1211 error: patch failed: dlls/sapi/tests/tts.c:112 error: patch failed: dlls/sapi/tts.c:45 error: patch failed: dlls/sapi/tests/tts.c:548 Task: Patch failed to apply
=== debian11 (build log) ===
error: patch failed: dlls/sapi/tests/tts.c:98 error: patch failed: dlls/sapi/tts.c:731 error: patch failed: dlls/sapi/tests/tts.c:206 error: patch failed: dlls/sapi/tts.c:1211 error: patch failed: dlls/sapi/tests/tts.c:112 error: patch failed: dlls/sapi/tts.c:45 error: patch failed: dlls/sapi/tests/tts.c:548 Task: Patch failed to apply
=== debian11b (build log) ===
error: patch failed: dlls/sapi/tests/tts.c:98 error: patch failed: dlls/sapi/tts.c:731 error: patch failed: dlls/sapi/tests/tts.c:206 error: patch failed: dlls/sapi/tts.c:1211 error: patch failed: dlls/sapi/tests/tts.c:112 error: patch failed: dlls/sapi/tts.c:45 error: patch failed: dlls/sapi/tests/tts.c:548 Task: Patch failed to apply
This merge request was closed by Shaun Ren.