-- v4: sapi: Introduce ISpTTSEngineSite stub. sapi: Implement synchronous ISpVoice::Speak. sapi: Partially implement ISpVoice::Speak SPF_ASYNC. sapi: Implement ISpVoice::Speak SPF_PURGEBEFORESPEAK. sapi: Reset empty event after queuing a task in async_queue_task. sapi: Handle queue not initialized in async_empty_queue.
From: Shaun Ren sren@codeweavers.com
--- include/sapiddk.idl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/include/sapiddk.idl b/include/sapiddk.idl index 46aebbb9a12..8f9abf4e117 100644 --- a/include/sapiddk.idl +++ b/include/sapiddk.idl @@ -72,7 +72,7 @@ typedef enum SPVESACTIONS ] interface ISpTTSEngineSite : ISpEventSink { - HRESULT GetActions(); + DWORD GetActions(); HRESULT Write([in] const void *pBuff, [in] ULONG cb, [out] ULONG *pcbWritten);
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/async.c | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/dlls/sapi/async.c b/dlls/sapi/async.c index 57ae89ad723..e09c6c64f9a 100644 --- a/dlls/sapi/async.c +++ b/dlls/sapi/async.c @@ -52,6 +52,8 @@ void async_empty_queue(struct async_queue *queue) { struct async_task *task, *next;
+ if (!queue->init) return; + EnterCriticalSection(&queue->cs); LIST_FOR_EACH_ENTRY_SAFE(task, next, &queue->tasks, struct async_task, entry) {
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/async.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/dlls/sapi/async.c b/dlls/sapi/async.c index e09c6c64f9a..491ca657c1a 100644 --- a/dlls/sapi/async.c +++ b/dlls/sapi/async.c @@ -165,6 +165,7 @@ HRESULT async_queue_task(struct async_queue *queue, struct async_task *task) list_add_tail(&queue->tasks, &task->entry); LeaveCriticalSection(&queue->cs);
+ ResetEvent(queue->empty); SetEvent(queue->wait);
return S_OK;
From: Shaun Ren sren@codeweavers.com
Also introduce an async task queue. --- dlls/sapi/tests/tts.c | 9 +++++++++ dlls/sapi/tts.c | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-)
diff --git a/dlls/sapi/tests/tts.c b/dlls/sapi/tests/tts.c index 534e5842f39..586869aa33e 100644 --- a/dlls/sapi/tests/tts.c +++ b/dlls/sapi/tests/tts.c @@ -102,6 +102,7 @@ static void test_spvoice(void) WCHAR *token_id = NULL, *default_token_id = NULL; LONG rate; USHORT volume; + ULONG stream_num; HRESULT hr;
if (waveOutGetNumDevs() == 0) { @@ -204,6 +205,14 @@ static void test_spvoice(void) hr = ISpVoice_SetVolume(voice, 101); ok(hr == E_INVALIDARG, "got %#lx.\n", hr);
+ hr = ISpVoice_Speak(voice, NULL, SPF_PURGEBEFORESPEAK, NULL); + ok(hr == S_OK, "got %#lx.\n", hr); + + 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); + ISpVoice_Release(voice); ISpMMSysAudio_Release(audio_out); } diff --git a/dlls/sapi/tts.c b/dlls/sapi/tts.c index b0b3bbd2eff..fda3f08d0da 100644 --- a/dlls/sapi/tts.c +++ b/dlls/sapi/tts.c @@ -45,6 +45,7 @@ struct speech_voice ISpTTSEngine *engine; USHORT volume; LONG rate; + struct async_queue queue; CRITICAL_SECTION cs; };
@@ -143,6 +144,7 @@ static ULONG WINAPI speech_voice_Release(ISpeechVoice *iface)
if (!ref) { + async_cancel_queue(&This->queue); if (This->output) ISpStreamFormat_Release(This->output); if (This->engine) ISpTTSEngine_Release(This->engine); DeleteCriticalSection(&This->cs); @@ -697,9 +699,40 @@ static HRESULT WINAPI spvoice_GetVoice(ISpVoice *iface, ISpObjectToken **token) return hr; }
-static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWORD flags, ULONG *number) +static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWORD flags, ULONG *stream_num_out) { - FIXME("(%p, %p, %#lx, %p): stub.\n", iface, contents, flags, number); + struct speech_voice *This = impl_from_ISpVoice(iface); + HRESULT hr; + + FIXME("(%p, %p, %#lx, %p): semi-stub.\n", iface, contents, flags, stream_num_out); + + if (flags & ~SPF_PURGEBEFORESPEAK) + { + FIXME("flags %#lx not implemented.\n", flags & ~SPF_PURGEBEFORESPEAK); + return E_NOTIMPL; + } + + if (flags & SPF_PURGEBEFORESPEAK) + { + ISpAudio *audio; + + EnterCriticalSection(&This->cs); + + if (This->output && SUCCEEDED(ISpStreamFormat_QueryInterface(This->output, &IID_ISpAudio, (void **)&audio))) + { + ISpAudio_SetState(audio, SPAS_CLOSED, 0); + ISpAudio_Release(audio); + } + + LeaveCriticalSection(&This->cs); + + async_empty_queue(&This->queue); + + if (!contents || !*contents) + return S_OK; + } + else if (!contents) + return E_POINTER;
return E_NOTIMPL; } @@ -962,6 +995,7 @@ HRESULT speech_voice_create(IUnknown *outer, REFIID iid, void **obj) This->engine = NULL; This->volume = 100; This->rate = 0; + memset(&This->queue, 0, sizeof(This->queue));
InitializeCriticalSection(&This->cs);
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/tts.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-)
diff --git a/dlls/sapi/tts.c b/dlls/sapi/tts.c index fda3f08d0da..bc4a225a3f5 100644 --- a/dlls/sapi/tts.c +++ b/dlls/sapi/tts.c @@ -699,16 +699,34 @@ static HRESULT WINAPI spvoice_GetVoice(ISpVoice *iface, ISpObjectToken **token) return hr; }
+struct speak_task +{ + struct async_task task; + + struct speech_voice *voice; + SPVTEXTFRAG *frag_list; + DWORD flags; +}; + +static void speak_proc(struct async_task *task) +{ + FIXME("(%p): stub.\n", task); +} + static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWORD flags, ULONG *stream_num_out) { struct speech_voice *This = impl_from_ISpVoice(iface); + SPVTEXTFRAG *frag; + struct speak_task *speak_task = NULL; + size_t contents_len, contents_size; HRESULT hr;
FIXME("(%p, %p, %#lx, %p): semi-stub.\n", iface, contents, flags, stream_num_out);
- if (flags & ~SPF_PURGEBEFORESPEAK) + flags &= ~SPF_IS_NOT_XML; + if (flags & ~(SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_NLP_SPEAK_PUNC)) { - FIXME("flags %#lx not implemented.\n", flags & ~SPF_PURGEBEFORESPEAK); + FIXME("flags %#lx not implemented.\n", flags & ~(SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_NLP_SPEAK_PUNC)); return E_NOTIMPL; }
@@ -734,7 +752,57 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR else if (!contents) return E_POINTER;
- return E_NOTIMPL; + if (!(flags & SPF_ASYNC)) + { + FIXME("Synchronous Speak not implemented.\n"); + return E_NOTIMPL; + } + + contents_len = wcslen(contents); + contents_size = sizeof(WCHAR) * (contents_len + 1); + + if (!This->output) + { + /* Create a new output stream with the default output. */ + if (FAILED(hr = ISpVoice_SetOutput(iface, NULL, TRUE))) + return hr; + } + + if (!This->engine) + { + /* Create a new engine with the default voice. */ + if (FAILED(hr = ISpVoice_SetVoice(iface, NULL))) + return hr; + } + + if (!(frag = heap_alloc(sizeof(*frag) + contents_size))) + return E_OUTOFMEMORY; + memset(frag, 0, sizeof(*frag)); + memcpy(frag + 1, contents, contents_size); + frag->State.eAction = SPVA_Speak; + frag->State.Volume = 100; + frag->pTextStart = (WCHAR *)(frag + 1); + frag->ulTextLen = contents_len; + frag->ulTextSrcOffset = 0; + speak_task = heap_alloc(sizeof(*speak_task)); + + speak_task->task.proc = speak_proc; + speak_task->voice = This; + speak_task->frag_list = frag; + speak_task->flags = flags & SPF_NLP_SPEAK_PUNC; + + if (FAILED(hr = async_queue_task(&This->queue, (struct async_task *)speak_task))) + { + WARN("Failed to queue task: %#lx.\n", hr); + goto fail; + } + + if (flags & SPF_ASYNC) + return S_OK; +fail: + heap_free(frag); + heap_free(speak_task); + return hr; }
static HRESULT WINAPI spvoice_SpeakStream(ISpVoice *iface, IStream *stream, DWORD flags, ULONG *number)
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/tts.c | 49 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-)
diff --git a/dlls/sapi/tts.c b/dlls/sapi/tts.c index bc4a225a3f5..fb2fc9b9991 100644 --- a/dlls/sapi/tts.c +++ b/dlls/sapi/tts.c @@ -699,9 +699,16 @@ static HRESULT WINAPI spvoice_GetVoice(ISpVoice *iface, ISpObjectToken **token) return hr; }
+struct async_result +{ + HANDLE done; + HRESULT hr; +}; + struct speak_task { struct async_task task; + struct async_result *result;
struct speech_voice *voice; SPVTEXTFRAG *frag_list; @@ -710,7 +717,15 @@ struct speak_task
static void speak_proc(struct async_task *task) { + struct speak_task *speak_task = (struct speak_task *)task; + FIXME("(%p): stub.\n", task); + + if (speak_task->result) + { + speak_task->result->hr = E_NOTIMPL; + SetEvent(speak_task->result->done); + } }
static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWORD flags, ULONG *stream_num_out) @@ -718,6 +733,7 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR struct speech_voice *This = impl_from_ISpVoice(iface); SPVTEXTFRAG *frag; struct speak_task *speak_task = NULL; + struct async_result *result = NULL; size_t contents_len, contents_size; HRESULT hr;
@@ -752,12 +768,6 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR else if (!contents) return E_POINTER;
- if (!(flags & SPF_ASYNC)) - { - FIXME("Synchronous Speak not implemented.\n"); - return E_NOTIMPL; - } - contents_len = wcslen(contents); contents_size = sizeof(WCHAR) * (contents_len + 1);
@@ -787,10 +797,23 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR speak_task = heap_alloc(sizeof(*speak_task));
speak_task->task.proc = speak_proc; + speak_task->result = NULL; speak_task->voice = This; speak_task->frag_list = frag; speak_task->flags = flags & SPF_NLP_SPEAK_PUNC;
+ if (!(flags & SPF_ASYNC)) + { + if (!(result = heap_alloc(sizeof(*result)))) + { + hr = E_OUTOFMEMORY; + goto fail; + } + result->hr = E_FAIL; + result->done = CreateEventW(NULL, FALSE, FALSE, NULL); + speak_task->result = result; + } + if (FAILED(hr = async_queue_task(&This->queue, (struct async_task *)speak_task))) { WARN("Failed to queue task: %#lx.\n", hr); @@ -799,9 +822,23 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR
if (flags & SPF_ASYNC) return S_OK; + else + { + WaitForSingleObject(result->done, INFINITE); + hr = result->hr; + CloseHandle(result->done); + heap_free(result); + return hr; + } + fail: heap_free(frag); heap_free(speak_task); + if (result) + { + CloseHandle(result->done); + heap_free(result); + } return hr; }
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/tests/tts.c | 5 ++ dlls/sapi/tts.c | 173 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+)
diff --git a/dlls/sapi/tests/tts.c b/dlls/sapi/tests/tts.c index 586869aa33e..ffe2e5f73a7 100644 --- a/dlls/sapi/tests/tts.c +++ b/dlls/sapi/tests/tts.c @@ -38,6 +38,7 @@ static void test_interfaces(void) { ISpeechVoice *speech_voice, *speech_voice2; IConnectionPointContainer *container; + ISpTTSEngineSite *site; ISpVoice *spvoice, *spvoice2; IDispatch *dispatch; IUnknown *unk; @@ -90,6 +91,10 @@ static void test_interfaces(void) EXPECT_REF(container, 2); IConnectionPointContainer_Release(container);
+ hr = ISpeechVoice_QueryInterface(speech_voice, &IID_ISpTTSEngineSite, + (void **)&site); + ok(hr == E_NOINTERFACE, "ISpeechVoice_QueryInterface for ISpTTSEngineSite returned: %#lx.\n", hr); + ISpeechVoice_Release(speech_voice); }
diff --git a/dlls/sapi/tts.c b/dlls/sapi/tts.c index fb2fc9b9991..489f31faa5d 100644 --- a/dlls/sapi/tts.c +++ b/dlls/sapi/tts.c @@ -43,6 +43,7 @@ struct speech_voice
ISpStreamFormat *output; ISpTTSEngine *engine; + ULONG cur_stream_num; USHORT volume; LONG rate; struct async_queue queue; @@ -64,6 +65,20 @@ static inline struct speech_voice *impl_from_IConnectionPointContainer(IConnecti return CONTAINING_RECORD(iface, struct speech_voice, IConnectionPointContainer_iface); }
+struct tts_engine_site +{ + ISpTTSEngineSite ISpTTSEngineSite_iface; + LONG ref; + + struct speech_voice *voice; + ULONG stream_num; +}; + +static inline struct tts_engine_site *impl_from_ISpTTSEngineSite(ISpTTSEngineSite *iface) +{ + return CONTAINING_RECORD(iface, struct tts_engine_site, ISpTTSEngineSite_iface); +} + static HRESULT create_default_token(const WCHAR *cat_id, ISpObjectToken **token) { ISpObjectTokenCategory *cat; @@ -712,6 +727,7 @@ struct speak_task
struct speech_voice *voice; SPVTEXTFRAG *frag_list; + ISpTTSEngineSite *site; DWORD flags; };
@@ -728,13 +744,17 @@ static void speak_proc(struct async_task *task) } }
+static HRESULT ttsenginesite_create(struct speech_voice *voice, ULONG stream_num, ISpTTSEngineSite **site); + static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWORD flags, ULONG *stream_num_out) { struct speech_voice *This = impl_from_ISpVoice(iface); + ISpTTSEngineSite *site = NULL; SPVTEXTFRAG *frag; struct speak_task *speak_task = NULL; struct async_result *result = NULL; size_t contents_len, contents_size; + ULONG stream_num; HRESULT hr;
FIXME("(%p, %p, %#lx, %p): semi-stub.\n", iface, contents, flags, stream_num_out); @@ -794,12 +814,21 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR frag->pTextStart = (WCHAR *)(frag + 1); frag->ulTextLen = contents_len; frag->ulTextSrcOffset = 0; + + stream_num = InterlockedIncrement((LONG *)&This->cur_stream_num); + if (FAILED(hr = ttsenginesite_create(This, stream_num, &site))) + { + FIXME("Failed to create ttsenginesite: %#lx.\n", hr); + goto fail; + } + speak_task = heap_alloc(sizeof(*speak_task));
speak_task->task.proc = speak_proc; speak_task->result = NULL; speak_task->voice = This; speak_task->frag_list = frag; + speak_task->site = site; speak_task->flags = flags & SPF_NLP_SPEAK_PUNC;
if (!(flags & SPF_ASYNC)) @@ -820,6 +849,9 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR goto fail; }
+ if (stream_num_out) + *stream_num_out = stream_num; + if (flags & SPF_ASYNC) return S_OK; else @@ -832,6 +864,7 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR }
fail: + if (site) ISpTTSEngineSite_Release(site); heap_free(frag); heap_free(speak_task); if (result) @@ -1032,6 +1065,145 @@ static const ISpVoiceVtbl spvoice_vtbl = spvoice_DisplayUI };
+/* ISpTTSEngineSite interface */ +static HRESULT WINAPI ttsenginesite_QueryInterface(ISpTTSEngineSite *iface, REFIID iid, void **obj) +{ + struct tts_engine_site *This = impl_from_ISpTTSEngineSite(iface); + + TRACE("(%p, %s %p).\n", iface, debugstr_guid(iid), obj); + + if (IsEqualIID(iid, &IID_IUnknown) || + IsEqualIID(iid, &IID_ISpTTSEngineSite)) + *obj = &This->ISpTTSEngineSite_iface; + else + { + *obj = NULL; + FIXME("interface %s not implemented.\n", debugstr_guid(iid)); + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown *)*obj); + return S_OK; +} + +static ULONG WINAPI ttsenginesite_AddRef(ISpTTSEngineSite *iface) +{ + struct tts_engine_site *This = impl_from_ISpTTSEngineSite(iface); + ULONG ref = InterlockedIncrement(&This->ref); + + TRACE("(%p): ref=%lu.\n", iface, ref); + + return ref; +} + +static ULONG WINAPI ttsenginesite_Release(ISpTTSEngineSite *iface) +{ + struct tts_engine_site *This = impl_from_ISpTTSEngineSite(iface); + + ULONG ref = InterlockedDecrement(&This->ref); + + TRACE("(%p): ref=%lu.\n", iface, ref); + + if (!ref) + { + if (This->voice) + ISpeechVoice_Release(&This->voice->ISpeechVoice_iface); + heap_free(This); + } + + return ref; +} + +static HRESULT WINAPI ttsenginesite_AddEvents(ISpTTSEngineSite *iface, const SPEVENT *events, ULONG count) +{ + FIXME("(%p, %p, %ld): stub.\n", iface, events, count); + + return S_OK; +} + +static HRESULT WINAPI ttsenginesite_GetEventInterest(ISpTTSEngineSite *iface, ULONGLONG *interest) +{ + FIXME("(%p, %p): stub.\n", iface, interest); + + return E_NOTIMPL; +} + +static DWORD WINAPI ttsenginesite_GetActions(ISpTTSEngineSite *iface) +{ + FIXME("(%p): stub.\n", iface); + + return SPVES_CONTINUE; +} + +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); + + return E_NOTIMPL; +} + +static HRESULT WINAPI ttsenginesite_GetRate(ISpTTSEngineSite *iface, long *rate) +{ + FIXME("(%p, %p): stub.\n", iface, rate); + + return E_NOTIMPL; +} + +static HRESULT WINAPI ttsenginesite_GetVolume(ISpTTSEngineSite *iface, USHORT *volume) +{ + FIXME("(%p, %p): stub.\n", iface, volume); + + return E_NOTIMPL; +} + +static HRESULT WINAPI ttsenginesite_GetSkipInfo(ISpTTSEngineSite *iface, SPVSKIPTYPE *type, long *skip_count) +{ + FIXME("(%p, %p, %p): stub.\n", iface, type, skip_count); + + return E_NOTIMPL; +} + +static HRESULT WINAPI ttsenginesite_CompleteSkip(ISpTTSEngineSite *iface, long num_skipped) +{ + FIXME("(%p, %ld): stub.\n", iface, num_skipped); + + return E_NOTIMPL; +} + +const static ISpTTSEngineSiteVtbl ttsenginesite_vtbl = +{ + ttsenginesite_QueryInterface, + ttsenginesite_AddRef, + ttsenginesite_Release, + ttsenginesite_AddEvents, + ttsenginesite_GetEventInterest, + ttsenginesite_GetActions, + ttsenginesite_Write, + ttsenginesite_GetRate, + ttsenginesite_GetVolume, + ttsenginesite_GetSkipInfo, + ttsenginesite_CompleteSkip +}; + +static HRESULT ttsenginesite_create(struct speech_voice *voice, ULONG stream_num, ISpTTSEngineSite **site) +{ + struct tts_engine_site *This = heap_alloc(sizeof(*This)); + + if (!This) return E_OUTOFMEMORY; + + This->ISpTTSEngineSite_iface.lpVtbl = &ttsenginesite_vtbl; + + This->ref = 1; + This->voice = voice; + This->stream_num = stream_num; + + ISpeechVoice_AddRef(&This->voice->ISpeechVoice_iface); + + *site = &This->ISpTTSEngineSite_iface; + + return S_OK; +} + /* IConnectionPointContainer interface */ static HRESULT WINAPI container_QueryInterface(IConnectionPointContainer *iface, REFIID iid, void **obj) { @@ -1098,6 +1270,7 @@ HRESULT speech_voice_create(IUnknown *outer, REFIID iid, void **obj)
This->output = NULL; This->engine = NULL; + This->cur_stream_num = 0; This->volume = 100; This->rate = 0; memset(&This->queue, 0, sizeof(This->queue));