Signed-off-by: Bernhard Kölbl besentv@gmail.com
-- v18: windows.media.speech: Implement Vosk create and release functions in the unixlib. windows.media.speech/tests: Allow the SpeechRecognizer creation to fail in Wine. windows.media.speech/tests: Get rid of duplicated hresult. windows.media.speech: Add unixlib stub.
From: Bernhard Kölbl besentv@gmail.com
Signed-off-by: Bernhard Kölbl besentv@gmail.com --- configure.ac | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac index 9ff7c5e8914..594f89f8263 100644 --- a/configure.ac +++ b/configure.ac @@ -59,6 +59,7 @@ AC_ARG_WITH(udev, AS_HELP_STRING([--without-udev],[do not use udev (plug an AC_ARG_WITH(unwind, AS_HELP_STRING([--without-unwind],[do not use the libunwind library (exception handling)])) AC_ARG_WITH(usb, AS_HELP_STRING([--without-usb],[do not use the libusb library])) AC_ARG_WITH(v4l2, AS_HELP_STRING([--without-v4l2],[do not use v4l2 (video capture)])) +AC_ARG_WITH(vosk, AS_HELP_STRING([--without-vosk],[do not use Vosk])) AC_ARG_WITH(vulkan, AS_HELP_STRING([--without-vulkan],[do not use Vulkan])) AC_ARG_WITH(xcomposite,AS_HELP_STRING([--without-xcomposite],[do not use the Xcomposite extension]), [if test "x$withval" = "xno"; then ac_cv_header_X11_extensions_Xcomposite_h=no; fi]) @@ -483,7 +484,8 @@ AC_CHECK_HEADERS(\ syscall.h \ utime.h \ valgrind/memcheck.h \ - valgrind/valgrind.h + valgrind/valgrind.h \ + vosk_api.h ) WINE_HEADER_MAJOR() AC_HEADER_STAT() @@ -1787,6 +1789,14 @@ then WINE_WARNING([No sound system was found. Windows applications will be silent.]) fi
+dnl **** Check for Vosk **** +if test x$with_vosk != xno +then + WINE_CHECK_SONAME(vosk,vosk_recognizer_new) +fi +WINE_NOTICE_WITH(vosk,[test x$ac_cv_lib_soname_vosk = x], + [libvosk ${notice_platform}development files not found, speech recognition won't be supported.]) + dnl *** Check for Vulkan *** if test "x$with_vulkan" != "xno" then
From: Bernhard Kölbl besentv@gmail.com
windows.media.speech: Add unixlib stub.
Signed-off-by: Bernhard Kölbl besentv@gmail.com --- dlls/windows.media.speech/Makefile.in | 2 + dlls/windows.media.speech/main.c | 24 +++++ dlls/windows.media.speech/private.h | 4 + dlls/windows.media.speech/unixlib.c | 134 ++++++++++++++++++++++++++ dlls/windows.media.speech/unixlib.h | 39 ++++++++ 5 files changed, 203 insertions(+) create mode 100644 dlls/windows.media.speech/unixlib.c create mode 100644 dlls/windows.media.speech/unixlib.h
diff --git a/dlls/windows.media.speech/Makefile.in b/dlls/windows.media.speech/Makefile.in index 10903cb1d7b..64376514d58 100644 --- a/dlls/windows.media.speech/Makefile.in +++ b/dlls/windows.media.speech/Makefile.in @@ -1,4 +1,5 @@ MODULE = windows.media.speech.dll +UNIXLIB = windows.media.speech.so IMPORTS = combase uuid
C_SRCS = \ @@ -8,6 +9,7 @@ C_SRCS = \ main.c \ recognizer.c \ synthesizer.c \ + unixlib.c \ vector.c
IDL_SRCS = classes.idl diff --git a/dlls/windows.media.speech/main.c b/dlls/windows.media.speech/main.c index e772a791588..c20ae4a0a1d 100644 --- a/dlls/windows.media.speech/main.c +++ b/dlls/windows.media.speech/main.c @@ -20,10 +20,34 @@ #include "initguid.h" #include "private.h"
+#include "unixlib.h" + #include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(speech);
+BOOL WINAPI DllMain( HINSTANCE instance, DWORD reason, void *reserved ) +{ + NTSTATUS status; + + switch (reason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(instance); + __wine_init_unix_call(); + + if ((status = WINE_UNIX_CALL(unix_speech_vosk_process_attach, NULL))) + WARN("Loading Vosk failed with status %#lx.\n", status); + + break; + case DLL_PROCESS_DETACH: + if (__wine_unixlib_handle) WINE_UNIX_CALL(unix_speech_vosk_process_detach, NULL); + break; + } + + return TRUE; +} + HRESULT WINAPI DllGetClassObject(REFCLSID clsid, REFIID riid, void **out) { FIXME("clsid %s, riid %s, out %p stub!\n", debugstr_guid(clsid), debugstr_guid(riid), out); diff --git a/dlls/windows.media.speech/private.h b/dlls/windows.media.speech/private.h index e80d73ec1fb..2f804fbf1a7 100644 --- a/dlls/windows.media.speech/private.h +++ b/dlls/windows.media.speech/private.h @@ -22,6 +22,10 @@
#include <stdarg.h>
+#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "winerror.h" +#include "winternl.h" #define COBJMACROS #include "corerror.h" #include "windef.h" diff --git a/dlls/windows.media.speech/unixlib.c b/dlls/windows.media.speech/unixlib.c new file mode 100644 index 00000000000..a1bbf03eddc --- /dev/null +++ b/dlls/windows.media.speech/unixlib.c @@ -0,0 +1,134 @@ +/* + * Unixlib for Windows.Media.Speech + * + * Copyright 2023 Bernhard Kölbl 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 + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#ifdef SONAME_LIBVOSK + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <vosk_api.h> + +#include <dlfcn.h> + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "winerror.h" +#include "winternl.h" + +#include "wine/debug.h" + +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(speech); +WINE_DECLARE_DEBUG_CHANNEL(winediag); + +static void *vosk_handle; +#define MAKE_FUNCPTR( f ) static typeof(f) * p_##f +MAKE_FUNCPTR(vosk_model_new); +MAKE_FUNCPTR(vosk_model_free); +MAKE_FUNCPTR(vosk_recognizer_new); +MAKE_FUNCPTR(vosk_recognizer_free); +#undef MAKE_FUNCPTR + +VoskModel *compat_vosk_model_new( const char *model_path ) +{ + return NULL; +} + +void compat_vosk_model_free( VoskModel *model ) +{ +} + +VoskRecognizer *compat_vosk_recognizer_new( VoskModel *model, float sample_rate ) +{ + return NULL; +} + +void compat_vosk_recognizer_free( VoskRecognizer *recognizer ) +{ +} + +static NTSTATUS speech_vosk_process_attach( void *args ) +{ + if (!(vosk_handle = dlopen(SONAME_LIBVOSK, RTLD_NOW))) + { + ERR_(winediag)("Wine is unable to load the Unix side dependencies for speech recognition. " + "Make sure Vosk is installed and up to date on your system and try again.\n"); + return STATUS_DLL_NOT_FOUND; + } + +#define LOAD_FUNCPTR( f ) \ + if (!(p_##f = dlsym(vosk_handle, #f))) \ + { \ + ERR("failed to load %s\n", #f); \ + goto error; \ + } + LOAD_FUNCPTR(vosk_model_new) + LOAD_FUNCPTR(vosk_model_free) + LOAD_FUNCPTR(vosk_recognizer_new) + LOAD_FUNCPTR(vosk_recognizer_free) +#undef LOAD_FUNCPTR + + return STATUS_SUCCESS; + +error: +#define LOAD_COMPATFUNC( f ) \ + p_##f = compat_##f; + LOAD_COMPATFUNC(vosk_model_new) + LOAD_COMPATFUNC(vosk_model_free) + LOAD_COMPATFUNC(vosk_recognizer_new) + LOAD_COMPATFUNC(vosk_recognizer_free) +#undef LOAD_COMPATFUNC + + dlclose(vosk_handle); + vosk_handle = NULL; + return STATUS_DLL_NOT_FOUND; +} + +static NTSTATUS speech_vosk_process_detach( void *args ) +{ + if (vosk_handle) + { + dlclose(vosk_handle); + vosk_handle = NULL; + } + return STATUS_SUCCESS; +} + +unixlib_entry_t __wine_unix_call_funcs[] = +{ + speech_vosk_process_attach, + speech_vosk_process_detach, +}; + +unixlib_entry_t __wine_unix_call_wow64_funcs[] = +{ + speech_vosk_process_attach, + speech_vosk_process_detach, +}; + +#endif /* SONAME_LIBVOSK */ diff --git a/dlls/windows.media.speech/unixlib.h b/dlls/windows.media.speech/unixlib.h new file mode 100644 index 00000000000..7b7c5523e09 --- /dev/null +++ b/dlls/windows.media.speech/unixlib.h @@ -0,0 +1,39 @@ +/* + * Unix library interface for Windows.Media.Speech + * + * Copyright 2023 Bernhard Kölbl 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 + */ + +#ifndef __WINE_WINDOWS_MEDIA_SPEECH_UNIXLIB_H +#define __WINE_WINDOWS_MEDIA_SPEECH_UNIXLIB_H + +#include <stdbool.h> +#include <stdint.h> + +#include "windef.h" +#include "winternl.h" +#include "wtypes.h" + +#include "wine/unixlib.h" + +enum unix_funcs +{ + unix_speech_vosk_process_attach, + unix_speech_vosk_process_detach, +}; + +#endif
From: Bernhard Kölbl besentv@gmail.com
Signed-off-by: Bernhard Kölbl besentv@gmail.com --- dlls/windows.media.speech/tests/speech.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index 6bc5a8b1751..c7c7b6eb040 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -42,7 +42,6 @@ #define AsyncStatus_Closed 4
#define SPERR_WINRT_INTERNAL_ERROR 0x800455a0 -#define SPERR_WINRT_INCORRECT_FORMAT 0x80131537
#define IHandler_RecognitionResult ITypedEventHandler_SpeechContinuousRecognitionSession_SpeechContinuousRecognitionResultGeneratedEventArgs #define IHandler_RecognitionResultVtbl ITypedEventHandler_SpeechContinuousRecognitionSession_SpeechContinuousRecognitionResultGeneratedEventArgsVtbl @@ -1005,7 +1004,7 @@ static void test_SpeechSynthesizer(void) operation_ss_stream = (void *)0xdeadbeef; hr = ISpeechSynthesizer_SynthesizeSsmlToStreamAsync(synthesizer, str, &operation_ss_stream); /* Broken on Win 8 + 8.1 */ - ok(hr == S_OK || broken(hr == SPERR_WINRT_INCORRECT_FORMAT), "ISpeechSynthesizer_SynthesizeSsmlToStreamAsync failed, hr %#lx\n", hr); + ok(hr == S_OK || broken(hr == COR_E_FORMAT), "ISpeechSynthesizer_SynthesizeSsmlToStreamAsync failed, hr %#lx\n", hr);
if (hr == S_OK) {
From: Bernhard Kölbl besentv@gmail.com
To allow for error handling of missing Unix-side dependencies.
Signed-off-by: Bernhard Kölbl besentv@gmail.com --- dlls/windows.media.speech/tests/speech.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index c7c7b6eb040..57e216b78d5 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -1192,7 +1192,7 @@ static void test_SpeechRecognizer(void) ok(ref == 1, "Got unexpected ref %lu.\n", ref);
hr = RoActivateInstance(hstr, &inspectable); - ok(hr == S_OK || broken(hr == SPERR_WINRT_INTERNAL_ERROR), "Got unexpected hr %#lx.\n", hr); + ok(hr == S_OK || hr == SPERR_WINRT_INTERNAL_ERROR, "Got unexpected hr %#lx.\n", hr);
if (hr == S_OK) { @@ -1411,7 +1411,7 @@ skip_operation: } else if (hr == SPERR_WINRT_INTERNAL_ERROR) /* Not sure when this triggers. Probably if a language pack is not installed. */ { - win_skip("Could not init SpeechRecognizer with default language!\n"); + skip("Could not init SpeechRecognizer with default language!\n"); }
done: @@ -1651,12 +1651,12 @@ static void test_Recognition(void) ok(hr == S_OK, "WindowsCreateString failed, hr %#lx.\n", hr);
hr = RoActivateInstance(hstr, &inspectable); - ok(hr == S_OK || broken(hr == SPERR_WINRT_INTERNAL_ERROR || hr == REGDB_E_CLASSNOTREG), "Got unexpected hr %#lx.\n", hr); + ok(hr == S_OK || hr == SPERR_WINRT_INTERNAL_ERROR || broken(hr == REGDB_E_CLASSNOTREG), "Got unexpected hr %#lx.\n", hr); WindowsDeleteString(hstr);
- if (FAILED(hr)) /* Win 8 and 8.1 and Win10 without enabled SR. */ + if (FAILED(hr)) /* Win 8 and 8.1 and Win10 without enabled SR. Wine with missing Unix side dependencies. */ { - win_skip("SpeechRecognizer cannot be activated!\n"); + skip("SpeechRecognizer cannot be activated!\n"); goto done; }
From: Bernhard Kölbl besentv@gmail.com
Signed-off-by: Bernhard Kölbl besentv@gmail.com --- dlls/windows.media.speech/Makefile.in | 2 +- dlls/windows.media.speech/private.h | 3 + dlls/windows.media.speech/recognizer.c | 42 +++++++ dlls/windows.media.speech/unixlib.c | 152 ++++++++++++++++++++++++- dlls/windows.media.speech/unixlib.h | 16 +++ 5 files changed, 213 insertions(+), 2 deletions(-)
diff --git a/dlls/windows.media.speech/Makefile.in b/dlls/windows.media.speech/Makefile.in index 64376514d58..455f81c0840 100644 --- a/dlls/windows.media.speech/Makefile.in +++ b/dlls/windows.media.speech/Makefile.in @@ -1,6 +1,6 @@ MODULE = windows.media.speech.dll UNIXLIB = windows.media.speech.so -IMPORTS = combase uuid +IMPORTS = combase uuid user32
C_SRCS = \ async.c \ diff --git a/dlls/windows.media.speech/private.h b/dlls/windows.media.speech/private.h index 2f804fbf1a7..62952478bdf 100644 --- a/dlls/windows.media.speech/private.h +++ b/dlls/windows.media.speech/private.h @@ -31,6 +31,7 @@ #include "windef.h" #include "winbase.h" #include "winstring.h" +#include "winuser.h" #include "objbase.h"
#include "activation.h" @@ -47,6 +48,8 @@
#include "wine/list.h"
+#define SPERR_WINRT_INTERNAL_ERROR 0x800455a0 + /* * * Windows.Media.SpeechRecognition diff --git a/dlls/windows.media.speech/recognizer.c b/dlls/windows.media.speech/recognizer.c index c2f386206b8..06133031d44 100644 --- a/dlls/windows.media.speech/recognizer.c +++ b/dlls/windows.media.speech/recognizer.c @@ -25,6 +25,9 @@
#include "wine/debug.h"
+#include "unixlib.h" +#include "wine/unixlib.h" + WINE_DEFAULT_DEBUG_CHANNEL(speech);
/* @@ -171,6 +174,8 @@ struct session IAudioCaptureClient *capture_client; WAVEFORMATEX capture_wfx;
+ speech_recognizer_handle unix_handle; + HANDLE worker_thread, worker_control_event, audio_buf_event; BOOLEAN worker_running, worker_paused; CRITICAL_SECTION cs; @@ -318,7 +323,9 @@ static ULONG WINAPI session_AddRef( ISpeechContinuousRecognitionSession *iface ) static ULONG WINAPI session_Release( ISpeechContinuousRecognitionSession *iface ) { struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + struct speech_release_recognizer_params release_params; ULONG ref = InterlockedDecrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref);
if (!ref) @@ -344,6 +351,9 @@ static ULONG WINAPI session_Release( ISpeechContinuousRecognitionSession *iface impl->cs.DebugInfo->Spare[0] = 0; DeleteCriticalSection(&impl->cs);
+ release_params.handle = impl->unix_handle; + WINE_UNIX_CALL(unix_speech_release_recognizer, &release_params); + IVector_ISpeechRecognitionConstraint_Release(impl->constraints); free(impl); } @@ -1079,6 +1089,35 @@ cleanup: return hr; }
+static HRESULT recognizer_factory_create_vosk_instance( struct session *session ) +{ + struct speech_create_recognizer_params create_params = { 0 }; + WCHAR locale[LOCALE_NAME_MAX_LENGTH]; + NTSTATUS status; + INT len; + + if (!(len = GetUserDefaultLocaleName(locale, LOCALE_NAME_MAX_LENGTH))) + return E_FAIL; + + if (CharLowerBuffW(locale, len) != len) + return E_FAIL; + + if (!WideCharToMultiByte(CP_ACP, 0, locale, len, create_params.locale, ARRAY_SIZE(create_params.locale), NULL, NULL)) + return HRESULT_FROM_WIN32(GetLastError()); + + create_params.sample_rate = (FLOAT)session->capture_wfx.nSamplesPerSec; + + if ((status = WINE_UNIX_CALL(unix_speech_create_recognizer, &create_params))) + { + ERR("Unable to create Vosk instance for locale %s, status %#lx. Speech recognition won't work.\n", debugstr_a(create_params.locale), status); + return SPERR_WINRT_INTERNAL_ERROR; + } + + session->unix_handle = create_params.handle; + + return S_OK; +} + static HRESULT WINAPI recognizer_factory_Create( ISpeechRecognizerFactory *iface, ILanguage *language, ISpeechRecognizer **speechrecognizer ) { struct recognizer *impl; @@ -1125,6 +1164,9 @@ static HRESULT WINAPI recognizer_factory_Create( ISpeechRecognizerFactory *iface if (FAILED(hr = recognizer_factory_create_audio_capture(session))) goto error;
+ if (FAILED(hr = recognizer_factory_create_vosk_instance(session))) + goto error; + InitializeCriticalSection(&session->cs); session->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": recognition_session.cs");
diff --git a/dlls/windows.media.speech/unixlib.c b/dlls/windows.media.speech/unixlib.c index a1bbf03eddc..8fd56b16ef5 100644 --- a/dlls/windows.media.speech/unixlib.c +++ b/dlls/windows.media.speech/unixlib.c @@ -26,13 +26,16 @@
#ifdef SONAME_LIBVOSK
+#include <stdbool.h> #include <stdio.h> #include <stdlib.h> -#include <string.h> #include <stdarg.h> +#include <string.h> #include <vosk_api.h>
+#include <dirent.h> #include <dlfcn.h> +#include <errno.h>
#include "ntstatus.h" #define WIN32_NO_STATUS @@ -119,16 +122,163 @@ static NTSTATUS speech_vosk_process_detach( void *args ) return STATUS_SUCCESS; }
+static inline speech_recognizer_handle vosk_recognizer_to_handle( VoskRecognizer *recognizer ) +{ + return (speech_recognizer_handle)(UINT_PTR)recognizer; +} + +static inline VoskRecognizer *vosk_recognizer_from_handle( speech_recognizer_handle handle ) +{ + return (VoskRecognizer *)(UINT_PTR)handle; +} + +static NTSTATUS find_model_by_locale_and_path( const char *path, const char *locale, VoskModel **model ) +{ + static const char *vosk_model_identifier_small = "vosk-model-small-"; + static const char *vosk_model_identifier = "vosk-model-"; + size_t ident_small_len = strlen(vosk_model_identifier_small); + size_t ident_len = strlen(vosk_model_identifier); + NTSTATUS status = STATUS_UNSUCCESSFUL; + char *dir_name, *model_path, *str; + struct dirent *dirent; + size_t path_len, len; + DIR *dir; + + TRACE("path %s, locale %s, model %p.\n", path, debugstr_a(locale), model); + + if (!path || !locale || (len = strlen(locale)) < 4) + return STATUS_UNSUCCESSFUL; + + if (!(dir = opendir(path))) + return STATUS_UNSUCCESSFUL; + + path_len = strlen(path); + str = strchr(locale, '-'); + *model = NULL; + + while ((dirent = readdir(dir))) + { + if (dirent->d_type != DT_DIR) + continue; + + dir_name = dirent->d_name; + + if (!strncmp(dir_name, vosk_model_identifier_small, ident_small_len)) + dir_name += ident_small_len; + else if (!strncmp(dir_name, vosk_model_identifier, ident_len)) + dir_name += ident_len; + else + continue; + + /* First match for lang and region (en-us), then only lang (en). */ + if (strncmp(dir_name, locale, len) && strncmp(dir_name, locale, str - locale)) + continue; + + if (!(model_path = malloc(path_len + 1 /* '/' */ + strlen(dirent->d_name) + 1))) + { + status = STATUS_NO_MEMORY; + break; + } + + sprintf(model_path, "%s/%s", path, dirent->d_name); + + TRACE("Trying to load Vosk model %s.\n", debugstr_a(model_path)); + + *model = p_vosk_model_new(model_path); + free(model_path); + + if (*model) + { + status = STATUS_SUCCESS; + break; + } + } + + closedir(dir); + + return status; +} + +static NTSTATUS find_model_by_locale( const char *locale, VoskModel **model ) +{ + const char *suffix = NULL; + char *env, *path = NULL; + NTSTATUS status; + + TRACE("locale %s, model %p.\n", debugstr_a(locale), model); + + if (!model) + return STATUS_UNSUCCESSFUL; + + if (!find_model_by_locale_and_path(getenv("VOSK_MODEL_PATH"), locale, model)) + return STATUS_SUCCESS; + if (!find_model_by_locale_and_path("/usr/share/vosk", locale, model)) + return STATUS_SUCCESS; + + if ((env = getenv("XDG_CACHE_HOME"))) + suffix = "/vosk"; + else if ((env = getenv("HOME"))) + suffix = "/.cache/vosk"; + else + return STATUS_UNSUCCESSFUL; + + if (!(path = malloc(strlen(env) + strlen(suffix) + 1))) + return STATUS_NO_MEMORY; + + sprintf(path, "%s%s", env, suffix); + status = find_model_by_locale_and_path(path, locale, model); + free(path); + + return status; +} + +static NTSTATUS speech_create_recognizer( void *args ) +{ + struct speech_create_recognizer_params *params = args; + VoskRecognizer *recognizer = NULL; + VoskModel *model = NULL; + NTSTATUS status = STATUS_SUCCESS; + + TRACE("args %p.\n", args); + + if ((status = find_model_by_locale(params->locale, &model))) + return status; + + if (!(recognizer = p_vosk_recognizer_new(model, params->sample_rate))) + status = STATUS_UNSUCCESSFUL; + + /* The model is kept alive inside the recognizer, so we can safely free our ref here. */ + p_vosk_model_free(model); + + params->handle = vosk_recognizer_to_handle(recognizer); + return status; +} + +static NTSTATUS speech_release_recognizer( void *args ) +{ + struct speech_release_recognizer_params *params = args; + + TRACE("args %p.\n", args); + + p_vosk_recognizer_free(vosk_recognizer_from_handle(params->handle)); + + return STATUS_SUCCESS; +} + unixlib_entry_t __wine_unix_call_funcs[] = { speech_vosk_process_attach, speech_vosk_process_detach, + speech_create_recognizer, + speech_release_recognizer, };
unixlib_entry_t __wine_unix_call_wow64_funcs[] = { speech_vosk_process_attach, speech_vosk_process_detach, + speech_create_recognizer, + speech_release_recognizer, };
#endif /* SONAME_LIBVOSK */ diff --git a/dlls/windows.media.speech/unixlib.h b/dlls/windows.media.speech/unixlib.h index 7b7c5523e09..72b7e015a63 100644 --- a/dlls/windows.media.speech/unixlib.h +++ b/dlls/windows.media.speech/unixlib.h @@ -30,10 +30,26 @@
#include "wine/unixlib.h"
+typedef UINT64 speech_recognizer_handle; + +struct speech_create_recognizer_params +{ + speech_recognizer_handle handle; + CHAR locale[LOCALE_NAME_MAX_LENGTH]; + FLOAT sample_rate; +}; + +struct speech_release_recognizer_params +{ + speech_recognizer_handle handle; +}; + enum unix_funcs { unix_speech_vosk_process_attach, unix_speech_vosk_process_detach, + unix_speech_create_recognizer, + unix_speech_release_recognizer, };
#endif
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/unixlib.c:
- LOAD_FUNCPTR(vosk_model_new)
- LOAD_FUNCPTR(vosk_model_free)
- LOAD_FUNCPTR(vosk_recognizer_new)
- LOAD_FUNCPTR(vosk_recognizer_free)
+#undef LOAD_FUNCPTR
- return STATUS_SUCCESS;
+error: +#define LOAD_COMPATFUNC( f ) \
- p_##f = compat_##f;
- LOAD_COMPATFUNC(vosk_model_new)
- LOAD_COMPATFUNC(vosk_model_free)
- LOAD_COMPATFUNC(vosk_recognizer_new)
- LOAD_COMPATFUNC(vosk_recognizer_free)
+#undef LOAD_COMPATFUNC
I don't understand what good this does? If any of these functions is missing it's not going to work, so you should print and return an error.
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/unixlib.c:
vosk_handle = NULL;
- }
- return STATUS_SUCCESS;
+}
+unixlib_entry_t __wine_unix_call_funcs[] = +{
- speech_vosk_process_attach,
- speech_vosk_process_detach,
+};
+unixlib_entry_t __wine_unix_call_wow64_funcs[] = +{
- speech_vosk_process_attach,
- speech_vosk_process_detach,
+};
You're missing the entry points now if vosk wasn't found at compile time, and I think the unixlib will then fail to load entirely. This was better done in the previous iterations I think.
On Mon Feb 20 18:17:53 2023 +0000, Rémi Bernon wrote:
You're missing the entry points now if vosk wasn't found at compile time, and I think the unixlib will then fail to load entirely. This was better done in the previous iterations I think.
Tbh I'm not sure what's better in the long run but for this iteration it works fine. See https://gitlab.winehq.org/wine/wine/-/merge_requests/2091#note_24688
On Mon Feb 20 18:17:52 2023 +0000, Rémi Bernon wrote:
I don't understand what good this does? If any of these functions is missing it's not going to work, so you should print and return an error.
The idea behind these, is to move the point of failing as far back as possible in case libvosk.so is missing, so we don't rely on error checking in DLLmain and can just work with the unix call return values.
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/main.c:
+#include "unixlib.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(speech);
+BOOL WINAPI DllMain( HINSTANCE instance, DWORD reason, void *reserved ) +{
- NTSTATUS status;
- switch (reason)
- {
- case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(instance);
__wine_init_unix_call();
You should probably check the returned status here.
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/main.c:
+BOOL WINAPI DllMain( HINSTANCE instance, DWORD reason, void *reserved ) +{
- NTSTATUS status;
- switch (reason)
- {
- case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(instance);
__wine_init_unix_call();
if ((status = WINE_UNIX_CALL(unix_speech_vosk_process_attach, NULL)))
WARN("Loading Vosk failed with status %#lx.\n", status);
break;
- case DLL_PROCESS_DETACH:
if (__wine_unixlib_handle) WINE_UNIX_CALL(unix_speech_vosk_process_detach, NULL);
I don't think you need to check `__wine_unixlib_handle`.
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/unixlib.h:
+#ifndef __WINE_WINDOWS_MEDIA_SPEECH_UNIXLIB_H +#define __WINE_WINDOWS_MEDIA_SPEECH_UNIXLIB_H
+#include <stdbool.h> +#include <stdint.h>
+#include "windef.h" +#include "winternl.h" +#include "wtypes.h"
+#include "wine/unixlib.h"
+enum unix_funcs +{
- unix_speech_vosk_process_attach,
- unix_speech_vosk_process_detach,
You still mention vosk here, is it on purpose?
On Mon Feb 20 18:48:19 2023 +0000, Bernhard Kölbl wrote:
Tbh I'm not sure what's better in the long run but for this iteration it works fine. See https://gitlab.winehq.org/wine/wine/-/merge_requests/2091#note_24688
I'm not sure to see how it's better, but as long as it doesn't prevent the library to load I guess it's okay.
On Mon Feb 20 18:58:32 2023 +0000, Rémi Bernon wrote:
You still mention vosk here, is it on purpose?
Yes, because we load Vosk specifically.
On Mon Feb 20 19:28:47 2023 +0000, Bernhard Kölbl wrote:
Yes, because we load Vosk specifically.
If at some point you want to change the backend, you'd load a different one and still call `process_attach` / `process_detach` all the same. I'm not sure it's very useful to expose the detail of what it does to the PE side?
On Mon Feb 20 18:55:31 2023 +0000, Bernhard Kölbl wrote:
The idea behind these, is to move the point of failing as far back as possible in case libvosk.so is missing, so we don't rely on error checking in DLLmain and can just work with the unix call return values.
You should make this explicit rather than trying to hide the problem under the carpet.
Simplest way would be to check for `vosk_handle` not being NULL at the start of each recognizer entry points, returning `STATUS_UNSUPPORTED`.
On Mon Feb 20 19:36:36 2023 +0000, Rémi Bernon wrote:
If at some point you want to change the backend, you'd load a different one and still call `process_attach` / `process_detach` all the same. I'm not sure it's very useful to expose the detail of what it does to the PE side?
Ah, my idea was to have different load functions for different used libs. e.g. In case we ever have speech synthesis, I'd add a `unix_speech_<SOMELIB>_process_attach`. (keeping different compile configs in mind)
On Mon Feb 20 20:21:14 2023 +0000, Bernhard Kölbl wrote:
Ah, my idea was to have different load functions for different used libs. e.g. In case we ever have speech synthesis, I'd add a `unix_speech_<SOMELIB>_process_attach`. (keeping different compile configs in mind)
You could do all the unix side initialization in a single `process_attach` call, loading each backend one after the other.
On Mon Feb 20 20:24:03 2023 +0000, Rémi Bernon wrote:
You could do all the unix side initialization in a single `process_attach` call, loading each backend one after the other.
Yeah, but SONAME_LIBXY may not be defined. Sure I can pull the define in, but that wasn't liked before.
On Mon Feb 20 20:26:20 2023 +0000, Bernhard Kölbl wrote:
Yeah, but SONAME_LIBXY may not be defined. Sure I can pull the define in, but that wasn't liked before.
I think if `process_attach` is going to load multiple libraries it is fine to have the #ifdef internal to it. Or optionally using dedicated #ifdef-ed helpers to load each library, like `winex11` does it, but kept on the unix side. You could do that later, for now I think only the name shouldn't specify vosk.