[PATCH v5 0/5] MR729: windows.media.speech: Initial recognition session work.
-- v5: windows.media.speech: Implement a worker thread for the recognition session. https://gitlab.winehq.org/wine/wine/-/merge_requests/729
From: Bernhard Kölbl <besentv(a)gmail.com> Signed-off-by: Bernhard Kölbl <besentv(a)gmail.com> --- dlls/windows.media.speech/recognizer.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/dlls/windows.media.speech/recognizer.c b/dlls/windows.media.speech/recognizer.c index bdcc57f883e..105ed2b0b05 100644 --- a/dlls/windows.media.speech/recognizer.c +++ b/dlls/windows.media.speech/recognizer.c @@ -156,6 +156,8 @@ struct session ISpeechContinuousRecognitionSession ISpeechContinuousRecognitionSession_iface; LONG ref; + IVector_ISpeechRecognitionConstraint *constraints; + struct list completed_handlers; struct list result_handlers; }; @@ -208,6 +210,7 @@ static ULONG WINAPI session_Release( ISpeechContinuousRecognitionSession *iface { typed_event_handlers_clear(&impl->completed_handlers); typed_event_handlers_clear(&impl->result_handlers); + IVector_ISpeechRecognitionConstraint_Release(impl->constraints); free(impl); } @@ -360,7 +363,6 @@ struct recognizer LONG ref; ISpeechContinuousRecognitionSession *session; - IVector_ISpeechRecognitionConstraint *constraints; }; /* @@ -424,7 +426,6 @@ static ULONG WINAPI recognizer_Release( ISpeechRecognizer *iface ) if (!ref) { ISpeechContinuousRecognitionSession_Release(impl->session); - IVector_ISpeechRecognitionConstraint_Release(impl->constraints); free(impl); } @@ -452,8 +453,11 @@ static HRESULT WINAPI recognizer_GetTrustLevel( ISpeechRecognizer *iface, TrustL static HRESULT WINAPI recognizer_get_Constraints( ISpeechRecognizer *iface, IVector_ISpeechRecognitionConstraint **vector ) { struct recognizer *impl = impl_from_ISpeechRecognizer(iface); + struct session *session = impl_from_ISpeechContinuousRecognitionSession(impl->session); + TRACE("iface %p, operation %p.\n", iface, vector); - IVector_ISpeechRecognitionConstraint_AddRef((*vector = impl->constraints)); + + IVector_ISpeechRecognitionConstraint_AddRef((*vector = session->constraints)); return S_OK; } @@ -797,18 +801,22 @@ static HRESULT WINAPI recognizer_factory_Create( ISpeechRecognizerFactory *iface if (language) FIXME("language parameter unused. Stub!\n"); + /* Init ISpeechContinuousRecognitionSession */ session->ISpeechContinuousRecognitionSession_iface.lpVtbl = &session_vtbl; session->ref = 1; + list_init(&session->completed_handlers); list_init(&session->result_handlers); + if (FAILED(hr = vector_inspectable_create(&constraints_iids, (IVector_IInspectable**)&session->constraints))) + goto error; + + /* Init ISpeechRecognizer */ impl->ISpeechRecognizer_iface.lpVtbl = &speech_recognizer_vtbl; impl->IClosable_iface.lpVtbl = &closable_vtbl; impl->ISpeechRecognizer2_iface.lpVtbl = &speech_recognizer2_vtbl; impl->session = &session->ISpeechContinuousRecognitionSession_iface; impl->ref = 1; - if (FAILED(hr = vector_inspectable_create(&constraints_iids, (IVector_IInspectable**)&impl->constraints))) - goto error; TRACE("created SpeechRecognizer %p.\n", impl); @@ -816,6 +824,7 @@ static HRESULT WINAPI recognizer_factory_Create( ISpeechRecognizerFactory *iface return S_OK; error: + if (session->constraints) IVector_ISpeechRecognitionConstraint_Release(session->constraints); free(session); free(impl); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/729
From: Bernhard Kölbl <besentv(a)gmail.com> Signed-off-by: Bernhard Kölbl <besentv(a)gmail.com> --- dlls/windows.media.speech/recognizer.c | 7 ++++++- dlls/windows.media.speech/tests/speech.c | 24 ++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/dlls/windows.media.speech/recognizer.c b/dlls/windows.media.speech/recognizer.c index 105ed2b0b05..a007c6af4b6 100644 --- a/dlls/windows.media.speech/recognizer.c +++ b/dlls/windows.media.speech/recognizer.c @@ -266,10 +266,15 @@ static HRESULT WINAPI session_StartWithModeAsync( ISpeechContinuousRecognitionSe return E_NOTIMPL; } +static HRESULT WINAPI stop_callback( IInspectable *invoker ) +{ + return S_OK; +} + static HRESULT WINAPI session_StopAsync( ISpeechContinuousRecognitionSession *iface, IAsyncAction **action ) { FIXME("iface %p, action %p stub!\n", iface, action); - return E_NOTIMPL; + return async_action_create(NULL, stop_callback, action); } static HRESULT WINAPI session_CancelAsync( ISpeechContinuousRecognitionSession *iface, IAsyncAction **action ) diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index 464faaf819a..ff35d75c3b5 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -1762,9 +1762,7 @@ static void test_Recognition(void) */ hr = ISpeechContinuousRecognitionSession_StopAsync(session, &action2); - todo_wine ok(hr == S_OK, "ISpeechContinuousRecognitionSession_StopAsync failed, hr %#lx.\n", hr); - - if (FAILED(hr)) goto skip_action; + ok(hr == S_OK, "ISpeechContinuousRecognitionSession_StopAsync failed, hr %#lx.\n", hr); async_void_handler_create_static(&action_handler); action_handler.event_block = CreateEventW(NULL, FALSE, FALSE, NULL); @@ -1776,40 +1774,38 @@ static void test_Recognition(void) put_param.handler = &action_handler.IAsyncActionCompletedHandler_iface; put_param.action = action2; put_thread = CreateThread(NULL, 0, action_put_completed_thread, &put_param, 0, NULL); - todo_wine ok(!WaitForSingleObject(action_handler.event_finished , 5000), "Wait for event_finished failed.\n"); + ok(!WaitForSingleObject(action_handler.event_finished , 5000), "Wait for event_finished failed.\n"); handler = (void *)0xdeadbeef; old_ref = action_handler.ref; hr = IAsyncAction_get_Completed(action2, &handler); - todo_wine ok(hr == S_OK, "IAsyncAction_get_Completed failed, hr %#lx.\n", hr); + ok(hr == S_OK, "IAsyncAction_get_Completed failed, hr %#lx.\n", hr); - todo_wine ok(handler == &action_handler.IAsyncActionCompletedHandler_iface || /* Broken on 1507. */ - broken(handler != NULL && handler != (void *)0xdeadbeef), "Handler was %p.\n", handler); + todo_wine ok(handler == &action_handler.IAsyncActionCompletedHandler_iface, "Handler was %p.\n", handler); ref = action_handler.ref - old_ref; todo_wine ok(ref == 1, "The ref was increased by %lu.\n", ref); - IAsyncActionCompletedHandler_Release(handler); + if (handler) IAsyncActionCompletedHandler_Release(handler); hr = IAsyncAction_QueryInterface(action2, &IID_IAsyncInfo, (void **)&info); - todo_wine ok(hr == S_OK, "IAsyncAction_QueryInterface failed, hr %#lx.\n", hr); + ok(hr == S_OK, "IAsyncAction_QueryInterface failed, hr %#lx.\n", hr); hr = IAsyncInfo_Close(info); /* If IAsyncInfo_Close would wait for the handler to finish, the test would get stuck here. */ - todo_wine ok(hr == S_OK, "IAsyncInfo_Close failed, hr %#lx.\n", hr); + ok(hr == S_OK, "IAsyncInfo_Close failed, hr %#lx.\n", hr); check_async_info((IInspectable *)action2, 3, AsyncStatus_Closed, S_OK); set = SetEvent(action_handler.event_block); - todo_wine ok(set == TRUE, "Event 'event_block' wasn't set.\n"); - todo_wine ok(!WaitForSingleObject(put_thread , 1000), "Wait for put_thread failed.\n"); + ok(set == TRUE, "Event 'event_block' wasn't set.\n"); + ok(!WaitForSingleObject(put_thread , 1000), "Wait for put_thread failed.\n"); IAsyncInfo_Release(info); CloseHandle(action_handler.event_finished); CloseHandle(action_handler.event_block); CloseHandle(put_thread); - todo_wine ok(action != action2, "actions were the same!\n"); + ok(action != action2, "actions were the same!\n"); IAsyncAction_Release(action2); -skip_action: IAsyncAction_Release(action); hr = ISpeechContinuousRecognitionSession_remove_ResultGenerated(session, token); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/729
From: Bernhard Kölbl <besentv(a)gmail.com> Signed-off-by: Bernhard Kölbl <besentv(a)gmail.com> --- dlls/windows.media.speech/recognizer.c | 7 ++++++- dlls/windows.media.speech/tests/speech.c | 11 ++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/dlls/windows.media.speech/recognizer.c b/dlls/windows.media.speech/recognizer.c index a007c6af4b6..71fb8aa8746 100644 --- a/dlls/windows.media.speech/recognizer.c +++ b/dlls/windows.media.speech/recognizer.c @@ -283,10 +283,15 @@ static HRESULT WINAPI session_CancelAsync( ISpeechContinuousRecognitionSession * return E_NOTIMPL; } +static HRESULT WINAPI pause_callback( IInspectable *invoker ) +{ + return S_OK; +} + static HRESULT WINAPI session_PauseAsync( ISpeechContinuousRecognitionSession *iface, IAsyncAction **action ) { FIXME("iface %p, action %p stub!\n", iface, action); - return E_NOTIMPL; + return async_action_create(NULL, pause_callback, action); } static HRESULT WINAPI session_Resume( ISpeechContinuousRecognitionSession *iface ) diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index ff35d75c3b5..8fa4c845d93 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -1761,6 +1761,15 @@ static void test_Recognition(void) * TODO: Use a loopback device together with prerecorded audio files to test the recognizer's functionality. */ + hr = ISpeechContinuousRecognitionSession_PauseAsync(session, &action2); + ok(hr == S_OK, "ISpeechContinuousRecognitionSession_PauseAsync failed, hr %#lx.\n", hr); + await_async_void(action2, &action_handler); + check_async_info((IInspectable *)action2, 3, Completed, S_OK); + IAsyncAction_Release(action2); + + hr = ISpeechContinuousRecognitionSession_Resume(session); + todo_wine ok(hr == S_OK, "ISpeechContinuousRecognitionSession_Resume failed, hr %#lx.\n", hr); + hr = ISpeechContinuousRecognitionSession_StopAsync(session, &action2); ok(hr == S_OK, "ISpeechContinuousRecognitionSession_StopAsync failed, hr %#lx.\n", hr); @@ -1792,7 +1801,7 @@ static void test_Recognition(void) hr = IAsyncInfo_Close(info); /* If IAsyncInfo_Close would wait for the handler to finish, the test would get stuck here. */ ok(hr == S_OK, "IAsyncInfo_Close failed, hr %#lx.\n", hr); - check_async_info((IInspectable *)action2, 3, AsyncStatus_Closed, S_OK); + check_async_info((IInspectable *)action2, 4, AsyncStatus_Closed, S_OK); set = SetEvent(action_handler.event_block); ok(set == TRUE, "Event 'event_block' wasn't set.\n"); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/729
From: Bernhard Kölbl <besentv(a)gmail.com> Signed-off-by: Bernhard Kölbl <besentv(a)gmail.com> --- dlls/windows.media.speech/tests/speech.c | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index 8fa4c845d93..ca1f464c1be 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -1619,6 +1619,7 @@ static void test_Recognition(void) struct iterator_hstring iterator_hstring; struct iterable_hstring iterable_hstring; EventRegistrationToken token = { .value = 0 }; + SpeechRecognizerState recog_state; HSTRING commands[3], hstr, tag; HANDLE put_thread; LONG ref, old_ref; @@ -1717,6 +1718,11 @@ static void test_Recognition(void) ok(hr == S_OK, "ISpeechContinuousRecognitionSession_add_ResultGenerated failed, hr %#lx.\n", hr); ok(token.value != 0xdeadbeef, "Got unexpexted token: %#I64x.\n", token.value); + recog_state = 0xdeadbeef; + hr = ISpeechRecognizer2_get_State(recognizer2, &recog_state); + todo_wine ok(hr == S_OK, "ISpeechRecognizer2_get_State failed, hr %#lx.\n", hr); + todo_wine ok(recog_state == SpeechRecognizerState_Idle, "recog_state was %u.\n", recog_state); + hr = ISpeechRecognizer_CompileConstraintsAsync(recognizer, &operation); ok(hr == S_OK, "ISpeechRecognizer_CompileConstraintsAsync failed, hr %#lx.\n", hr); await_async_inspectable((IAsyncOperation_IInspectable *)operation, @@ -1757,6 +1763,11 @@ static void test_Recognition(void) IAsyncInfo_Release(info); + recog_state = 0xdeadbeef; + hr = ISpeechRecognizer2_get_State(recognizer2, &recog_state); + todo_wine ok(hr == S_OK, "ISpeechRecognizer2_get_State failed, hr %#lx.\n", hr); + todo_wine ok(recog_state == SpeechRecognizerState_Capturing, "recog_state was %u.\n", recog_state); + /* * TODO: Use a loopback device together with prerecorded audio files to test the recognizer's functionality. */ @@ -1767,9 +1778,20 @@ static void test_Recognition(void) check_async_info((IInspectable *)action2, 3, Completed, S_OK); IAsyncAction_Release(action2); + recog_state = 0xdeadbeef; + hr = ISpeechRecognizer2_get_State(recognizer2, &recog_state); + todo_wine ok(hr == S_OK, "ISpeechRecognizer2_get_State failed, hr %#lx.\n", hr); + todo_wine ok(recog_state == SpeechRecognizerState_Paused || /* Broken on Win10 1507 */ + broken(recog_state == SpeechRecognizerState_Capturing) , "recog_state was %u.\n", recog_state); + hr = ISpeechContinuousRecognitionSession_Resume(session); todo_wine ok(hr == S_OK, "ISpeechContinuousRecognitionSession_Resume failed, hr %#lx.\n", hr); + recog_state = 0xdeadbeef; + hr = ISpeechRecognizer2_get_State(recognizer2, &recog_state); + todo_wine ok(hr == S_OK, "ISpeechRecognizer2_get_State failed, hr %#lx.\n", hr); + todo_wine ok(recog_state == SpeechRecognizerState_Capturing, "recog_state was %u.\n", recog_state); + hr = ISpeechContinuousRecognitionSession_StopAsync(session, &action2); ok(hr == S_OK, "ISpeechContinuousRecognitionSession_StopAsync failed, hr %#lx.\n", hr); @@ -1817,6 +1839,11 @@ static void test_Recognition(void) IAsyncAction_Release(action2); IAsyncAction_Release(action); + recog_state = 0xdeadbeef; + hr = ISpeechRecognizer2_get_State(recognizer2, &recog_state); + todo_wine ok(hr == S_OK, "ISpeechRecognizer2_get_State failed, hr %#lx.\n", hr); + todo_wine ok(recog_state == SpeechRecognizerState_Idle, "recog_state was %u.\n", recog_state); + hr = ISpeechContinuousRecognitionSession_remove_ResultGenerated(session, token); ok(hr == S_OK, "ISpeechContinuousRecognitionSession_remove_ResultGenerated failed, hr %#lx.\n", hr); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/729
From: Bernhard Kölbl <besentv(a)gmail.com> Signed-off-by: Bernhard Kölbl <besentv(a)gmail.com> --- dlls/windows.media.speech/recognizer.c | 156 +++++++++++++++++++++-- dlls/windows.media.speech/tests/speech.c | 26 +++- 2 files changed, 171 insertions(+), 11 deletions(-) diff --git a/dlls/windows.media.speech/recognizer.c b/dlls/windows.media.speech/recognizer.c index 71fb8aa8746..17c15804333 100644 --- a/dlls/windows.media.speech/recognizer.c +++ b/dlls/windows.media.speech/recognizer.c @@ -17,6 +17,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ +#define E_INVALIDOPERATION 0x80131509 + #include "private.h" #include "wine/debug.h" @@ -160,6 +162,11 @@ struct session struct list completed_handlers; struct list result_handlers; + + HANDLE session_thread; + HANDLE session_paused_event, session_resume_event; + BOOLEAN session_running, session_paused; + CRITICAL_SECTION cs; }; /* @@ -173,6 +180,51 @@ static inline struct session *impl_from_ISpeechContinuousRecognitionSession( ISp return CONTAINING_RECORD(iface, struct session, ISpeechContinuousRecognitionSession_iface); } +static DWORD CALLBACK session_thread_cb( void *args ) +{ + ISpeechContinuousRecognitionSession *iface = args; + struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + BOOLEAN running, paused = FALSE; + + EnterCriticalSection(&impl->cs); + running = impl->session_running; + LeaveCriticalSection(&impl->cs); + + while (running) + { + EnterCriticalSection(&impl->cs); + paused = impl->session_paused; + running = impl->session_running; + LeaveCriticalSection(&impl->cs); + + /* TODO: Send mic data to recognizer. */ + + if (paused) + { + TRACE("paused!\n"); + SetEvent(impl->session_paused_event); + WaitForSingleObject(impl->session_resume_event, INFINITE); + } + + Sleep(500); /* Sleep to slow down spinning for now. */ + } + + return 0; +} + +static HRESULT WINAPI shutdown_session_thread( ISpeechContinuousRecognitionSession *iface ) +{ + struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + + SetEvent(impl->session_resume_event); /* Set the resume even just in case the session was paused. */ + if (WaitForSingleObject(impl->session_thread, 1000)) + ERR("Wait for shutting down the ISpeechContinuousRecognitionSession timed out!\n"); + CloseHandle(impl->session_thread); + impl->session_thread = NULL; + + return S_OK; +} + static HRESULT WINAPI session_QueryInterface( ISpeechContinuousRecognitionSession *iface, REFIID iid, void **out ) { struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); @@ -208,8 +260,21 @@ static ULONG WINAPI session_Release( ISpeechContinuousRecognitionSession *iface if (!ref) { + BOOLEAN running = FALSE; + + EnterCriticalSection(&impl->cs); + running = impl->session_running; + LeaveCriticalSection(&impl->cs); + + if (running) shutdown_session_thread(iface); + typed_event_handlers_clear(&impl->completed_handlers); typed_event_handlers_clear(&impl->result_handlers); + CloseHandle(impl->session_paused_event); + CloseHandle(impl->session_resume_event); + impl->cs.DebugInfo->Spare[0] = 0; + DeleteCriticalSection(&impl->cs); + IVector_ISpeechRecognitionConstraint_Release(impl->constraints); free(impl); } @@ -249,13 +314,33 @@ static HRESULT WINAPI session_set_AutoStopSilenceTimeout( ISpeechContinuousRecog static HRESULT WINAPI start_callback( IInspectable *invoker ) { - return S_OK; + struct session *impl = impl_from_ISpeechContinuousRecognitionSession((ISpeechContinuousRecognitionSession *)invoker); + HRESULT hr = S_OK; + + impl->session_running = TRUE; + impl->session_thread = CreateThread(NULL, 0, session_thread_cb, impl, 0, NULL); + + return hr; } static HRESULT WINAPI session_StartAsync( ISpeechContinuousRecognitionSession *iface, IAsyncAction **action ) { - FIXME("iface %p, action %p stub!\n", iface, action); - return async_action_create(NULL, start_callback, action); + struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + HRESULT hr = S_OK; + + TRACE("iface %p, action %p stub!\n", iface, action); + + *action = NULL; + + EnterCriticalSection(&impl->cs); + if (!impl->session_thread && !impl->session_running && !impl->session_paused) + { + hr = async_action_create((IInspectable *)iface, start_callback, action); + } + else hr = E_INVALIDOPERATION; + LeaveCriticalSection(&impl->cs); + + return hr; } static HRESULT WINAPI session_StartWithModeAsync( ISpeechContinuousRecognitionSession *iface, @@ -268,13 +353,30 @@ static HRESULT WINAPI session_StartWithModeAsync( ISpeechContinuousRecognitionSe static HRESULT WINAPI stop_callback( IInspectable *invoker ) { + shutdown_session_thread((ISpeechContinuousRecognitionSession *)invoker); return S_OK; } static HRESULT WINAPI session_StopAsync( ISpeechContinuousRecognitionSession *iface, IAsyncAction **action ) { - FIXME("iface %p, action %p stub!\n", iface, action); - return async_action_create(NULL, stop_callback, action); + struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + HRESULT hr = E_INVALIDOPERATION; + + TRACE("iface %p, action %p stub!\n", iface, action); + + *action = NULL; + + EnterCriticalSection(&impl->cs); + if (impl->session_thread && impl->session_running && !impl->session_paused) + { + impl->session_running = FALSE; + LeaveCriticalSection(&impl->cs); + + hr = async_action_create((IInspectable *)iface, stop_callback, action); + } + else LeaveCriticalSection(&impl->cs); + + return hr; } static HRESULT WINAPI session_CancelAsync( ISpeechContinuousRecognitionSession *iface, IAsyncAction **action ) @@ -285,19 +387,45 @@ static HRESULT WINAPI session_CancelAsync( ISpeechContinuousRecognitionSession * static HRESULT WINAPI pause_callback( IInspectable *invoker ) { + struct session *impl = impl_from_ISpeechContinuousRecognitionSession((ISpeechContinuousRecognitionSession *)invoker); + + EnterCriticalSection(&impl->cs); + if (impl->session_thread && impl->session_running && !impl->session_paused) + { + impl->session_paused = TRUE; + LeaveCriticalSection(&impl->cs); + + WaitForSingleObject(impl->session_paused_event, INFINITE); + } + else LeaveCriticalSection(&impl->cs); + return S_OK; } static HRESULT WINAPI session_PauseAsync( ISpeechContinuousRecognitionSession *iface, IAsyncAction **action ) { - FIXME("iface %p, action %p stub!\n", iface, action); - return async_action_create(NULL, pause_callback, action); + TRACE("iface %p, action %p stub!\n", iface, action); + + return async_action_create((IInspectable *)iface, pause_callback, action); } static HRESULT WINAPI session_Resume( ISpeechContinuousRecognitionSession *iface ) { - FIXME("iface %p stub!\n", iface); - return E_NOTIMPL; + struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + + TRACE("iface %p stub!\n", iface); + + EnterCriticalSection(&impl->cs); + if (impl->session_thread && impl->session_running && impl->session_paused) + { + impl->session_paused = FALSE; + LeaveCriticalSection(&impl->cs); + + SetEvent(impl->session_resume_event); + } + else LeaveCriticalSection(&impl->cs); + + return S_OK; } static HRESULT WINAPI session_add_Completed( ISpeechContinuousRecognitionSession *iface, @@ -814,10 +942,17 @@ static HRESULT WINAPI recognizer_factory_Create( ISpeechRecognizerFactory *iface /* Init ISpeechContinuousRecognitionSession */ session->ISpeechContinuousRecognitionSession_iface.lpVtbl = &session_vtbl; session->ref = 1; + session->session_paused_event = CreateEventW(NULL, FALSE, FALSE, NULL); + session->session_resume_event = CreateEventW(NULL, FALSE, FALSE, NULL); + if (FAILED(hr = HRESULT_FROM_WIN32(GetLastError())) || !session->session_paused_event || !session->session_resume_event) + goto error; list_init(&session->completed_handlers); list_init(&session->result_handlers); + InitializeCriticalSection(&session->cs); + session->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": recognition_session.cs"); + if (FAILED(hr = vector_inspectable_create(&constraints_iids, (IVector_IInspectable**)&session->constraints))) goto error; @@ -835,6 +970,9 @@ static HRESULT WINAPI recognizer_factory_Create( ISpeechRecognizerFactory *iface error: if (session->constraints) IVector_ISpeechRecognitionConstraint_Release(session->constraints); + CloseHandle(session->session_resume_event); + CloseHandle(session->session_paused_event); + DeleteCriticalSection(&session->cs); free(session); free(impl); diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index ca1f464c1be..3a3b4767021 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -40,6 +40,7 @@ #define AsyncStatus_Closed 4 +#define E_INVALIDOPERATION 0x80131509 #define SPERR_WINRT_INTERNAL_ERROR 0x800455a0 #define SPERR_WINRT_INCORRECT_FORMAT 0x80131537 @@ -1741,6 +1742,11 @@ static void test_Recognition(void) await_async_void(action, &action_handler); + action2 = (void *)0xdeadbeef; + hr = ISpeechContinuousRecognitionSession_StartAsync(session, &action2); + ok(hr == E_INVALIDOPERATION, "ISpeechContinuousRecognitionSession_StartAsync failed, hr %#lx.\n", hr); + ok(action2 == NULL, "action2 was %p.\n", action2); + hr = IAsyncAction_QueryInterface(action, &IID_IAsyncInfo, (void **)&info); ok(hr == S_OK, "IAsyncAction_QueryInterface failed, hr %#lx.\n", hr); check_async_info((IInspectable *)action, 1, Completed, S_OK); @@ -1784,8 +1790,19 @@ static void test_Recognition(void) todo_wine ok(recog_state == SpeechRecognizerState_Paused || /* Broken on Win10 1507 */ broken(recog_state == SpeechRecognizerState_Capturing) , "recog_state was %u.\n", recog_state); + /* Check what happens if we try to pause again, when the session is already paused. */ + hr = ISpeechContinuousRecognitionSession_PauseAsync(session, &action2); + ok(hr == S_OK, "ISpeechContinuousRecognitionSession_PauseAsync failed, hr %#lx.\n", hr); + await_async_void(action2, &action_handler); + check_async_info((IInspectable *)action2, 4, Completed, S_OK); + IAsyncAction_Release(action2); + + hr = ISpeechContinuousRecognitionSession_Resume(session); + ok(hr == S_OK, "ISpeechContinuousRecognitionSession_Resume failed, hr %#lx.\n", hr); + + /* Resume when already resumed. */ hr = ISpeechContinuousRecognitionSession_Resume(session); - todo_wine ok(hr == S_OK, "ISpeechContinuousRecognitionSession_Resume failed, hr %#lx.\n", hr); + ok(hr == S_OK, "ISpeechContinuousRecognitionSession_Resume failed, hr %#lx.\n", hr); recog_state = 0xdeadbeef; hr = ISpeechRecognizer2_get_State(recognizer2, &recog_state); @@ -1823,7 +1840,7 @@ static void test_Recognition(void) hr = IAsyncInfo_Close(info); /* If IAsyncInfo_Close would wait for the handler to finish, the test would get stuck here. */ ok(hr == S_OK, "IAsyncInfo_Close failed, hr %#lx.\n", hr); - check_async_info((IInspectable *)action2, 4, AsyncStatus_Closed, S_OK); + check_async_info((IInspectable *)action2, 5, AsyncStatus_Closed, S_OK); set = SetEvent(action_handler.event_block); ok(set == TRUE, "Event 'event_block' wasn't set.\n"); @@ -1844,6 +1861,11 @@ static void test_Recognition(void) todo_wine ok(hr == S_OK, "ISpeechRecognizer2_get_State failed, hr %#lx.\n", hr); todo_wine ok(recog_state == SpeechRecognizerState_Idle, "recog_state was %u.\n", recog_state); + /* Test stopping, when already stopped. */ + hr = ISpeechContinuousRecognitionSession_StopAsync(session, &action); + ok(hr == E_INVALIDOPERATION, "ISpeechContinuousRecognitionSession_StopAsync failed, hr %#lx.\n", hr); + ok(action == NULL, "action was %p.\n", action); + hr = ISpeechContinuousRecognitionSession_remove_ResultGenerated(session, token); ok(hr == S_OK, "ISpeechContinuousRecognitionSession_remove_ResultGenerated failed, hr %#lx.\n", hr); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/729
R��mi Bernon (@rbernon) commented about dlls/windows.media.speech/tests/speech.c:
#define AsyncStatus_Closed 4
+#define E_INVALIDOPERATION 0x80131509
This is actually `COR_E_INVALIDOPERATION`, which is already defined. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/729#note_7745
R��mi Bernon (@rbernon) commented about dlls/windows.media.speech/recognizer.c:
+ running = impl->session_running; + LeaveCriticalSection(&impl->cs); + + /* TODO: Send mic data to recognizer. */ + + if (paused) + { + TRACE("paused!\n"); + SetEvent(impl->session_paused_event); + WaitForSingleObject(impl->session_resume_event, INFINITE); + } + + Sleep(500); /* Sleep to slow down spinning for now. */ + } + + return 0; I believe a more canonical way to write this thread would be something like:
EnterCriticalSection(&impl->cs);
while (impl->session_running)
{
if (impl->session_paused)
{
SleepConditionVariableCS(&impl->cv, &impl->cs, INFINITE);
continue;
}
/* TODO: Send mic data to recognizer. */
Sleep(500); /* Sleep to slow down spinning for now. */
}
LeaveCriticalSection(&impl->cs);
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/729#note_7746
R��mi Bernon (@rbernon) commented about dlls/windows.media.speech/recognizer.c:
+ TRACE("paused!\n"); + SetEvent(impl->session_paused_event); + WaitForSingleObject(impl->session_resume_event, INFINITE); + } + + Sleep(500); /* Sleep to slow down spinning for now. */ + } + + return 0; +} + +static HRESULT WINAPI shutdown_session_thread( ISpeechContinuousRecognitionSession *iface ) +{ + struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + + SetEvent(impl->session_resume_event); /* Set the resume even just in case the session was paused. */ You would not need to set anything here, as it would be done in `session_Stop` / `session_Release`. Maybe rename that helper to `join_session_thread`.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/729#note_7747
R��mi Bernon (@rbernon) commented about dlls/windows.media.speech/recognizer.c:
static HRESULT WINAPI start_callback( IInspectable *invoker ) { - return S_OK; + struct session *impl = impl_from_ISpeechContinuousRecognitionSession((ISpeechContinuousRecognitionSession *)invoker); + HRESULT hr = S_OK; + + impl->session_running = TRUE; + impl->session_thread = CreateThread(NULL, 0, session_thread_cb, impl, 0, NULL);
You probably want to check that `CreateThread` succeeded, then eventually clear `session_running` and return an error. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/729#note_7748
R��mi Bernon (@rbernon) commented about dlls/windows.media.speech/recognizer.c:
{ - FIXME("iface %p stub!\n", iface); - return E_NOTIMPL; + struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + + TRACE("iface %p stub!\n", iface); + + EnterCriticalSection(&impl->cs); + if (impl->session_thread && impl->session_running && impl->session_paused) + { + impl->session_paused = FALSE; + LeaveCriticalSection(&impl->cs); + + SetEvent(impl->session_resume_event); + } + else LeaveCriticalSection(&impl->cs); That would be instead:
EnterCriticalSection(&impl->cs);
if (impl->session_running) impl->session_paused = FALSE;
LeaveCriticalSection(&impl->cs);
WakeConditionVariable(aimpl->cv);
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/729#note_7749
R��mi Bernon (@rbernon) commented about dlls/windows.media.speech/recognizer.c:
static HRESULT WINAPI pause_callback( IInspectable *invoker ) { + struct session *impl = impl_from_ISpeechContinuousRecognitionSession((ISpeechContinuousRecognitionSession *)invoker); + + EnterCriticalSection(&impl->cs); + if (impl->session_thread && impl->session_running && !impl->session_paused) + { + impl->session_paused = TRUE; + LeaveCriticalSection(&impl->cs); + + WaitForSingleObject(impl->session_paused_event, INFINITE); + } + else LeaveCriticalSection(&impl->cs);
That would be instead: ```c++ EnterCriticalSection(&impl->cs); if (impl->session_running) impl->session_paused = TRUE; LeaveCriticalSection(&impl->cs); WakeConditionVariable(aimpl->cv); ``` -- https://gitlab.winehq.org/wine/wine/-/merge_requests/729#note_7750
R��mi Bernon (@rbernon) commented about dlls/windows.media.speech/recognizer.c:
+ struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + HRESULT hr = E_INVALIDOPERATION; + + TRACE("iface %p, action %p stub!\n", iface, action); + + *action = NULL; + + EnterCriticalSection(&impl->cs); + if (impl->session_thread && impl->session_running && !impl->session_paused) + { + impl->session_running = FALSE; + LeaveCriticalSection(&impl->cs); + + hr = async_action_create((IInspectable *)iface, stop_callback, action); + } + else LeaveCriticalSection(&impl->cs); That would be instead:
EnterCriticalSection(&impl->cs);
if ((running = impl->session_running))
impl->session_running = FALSE;
LeaveCriticalSection(&impl->cs);
if (!running)
hr = E_INVALIDOPERATION;
else
{
WakeConditionVariable(&impl->cv);
hr = async_action_create((IInspectable *)iface, stop_callback, action);
}
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/729#note_7751
R��mi Bernon (@rbernon) commented about dlls/windows.media.speech/recognizer.c:
- FIXME("iface %p, action %p stub!\n", iface, action); - return async_action_create(NULL, start_callback, action); + struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + HRESULT hr = S_OK; + + TRACE("iface %p, action %p stub!\n", iface, action); + + *action = NULL; + + EnterCriticalSection(&impl->cs); + if (!impl->session_thread && !impl->session_running && !impl->session_paused) + { + hr = async_action_create((IInspectable *)iface, start_callback, action); + } + else hr = E_INVALIDOPERATION; + LeaveCriticalSection(&impl->cs); That would be instead:
EnterCriticalSection(&impl->cs);
if (!(running = impl->session_running))
impl->session_running = TRUE;
LeaveCriticalSection(&impl->cs);
if (running)
hr = E_INVALIDOPERATION;
else
hr = async_action_create((IInspectable *)iface, start_callback, action);
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/729#note_7752
R��mi Bernon (@rbernon) commented about dlls/windows.media.speech/recognizer.c:
if (!ref) { + BOOLEAN running = FALSE; + + EnterCriticalSection(&impl->cs); + running = impl->session_running; + LeaveCriticalSection(&impl->cs); + + if (running) shutdown_session_thread(iface);
You'd need to set `impl->session_running` to `FALSE` and `WakeConditionVariable` too before joining the thread. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/729#note_7753
participants (2)
-
Bernhard Kölbl -
Rémi Bernon