Second commit is to be removed.
Signed-off-by: Bernhard Kölbl besentv@gmail.com
-- v4: 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 a unixlib stub.
From: Bernhard Kölbl besentv@gmail.com
Signed-off-by: Bernhard Kölbl besentv@gmail.com --- configure.ac | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac index 9ff7c5e8914..e9264e74801 100644 --- a/configure.ac +++ b/configure.ac @@ -59,6 +59,8 @@ 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]), + [if test "x$withval" = "xno"; then ac_cv_header_vosk_api_h=no; fi]) 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 +485,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 +1790,17 @@ then WINE_WARNING([No sound system was found. Windows applications will be silent.]) fi
+dnl **** Check for Vosk **** +if test "$ac_cv_header_vosk_api_h" = "yes" +then + WINE_CHECK_SONAME(vosk,vosk_recognizer_new,[AC_SUBST(VOSK_LIBS,"-lvosk") + ac_cv_lib_vosk=yes + AC_DEFINE_UNQUOTED(HAVE_VOSK,1,[Define to 1 if Vosk is available])],,) +fi +WINE_NOTICE_WITH(vosk,[test "x$ac_cv_lib_vosk" != xyes], + [libvosk ${notice_platform}development files not found (or too old), Vosk aka speech recognition won't be supported.], + [enable_vosk]) + dnl *** Check for Vulkan *** if test "x$with_vulkan" != "xno" then
From: Bernhard Kölbl besentv@gmail.com
--- configure | 90 +++++++++++++++++++++++++++++++++++++++++++++ include/config.h.in | 9 +++++ 2 files changed, 99 insertions(+)
diff --git a/configure b/configure index c1c6c4cf93b..28de5ae1851 100755 --- a/configure +++ b/configure @@ -656,6 +656,7 @@ RT_LIBS WINELOADER_PROGRAMS DELAYLOADFLAG MSVCRTFLAGS +VOSK_LIBS NETAPI_LIBS NETAPI_CFLAGS PROCSTAT_LIBS @@ -936,6 +937,7 @@ with_udev with_unwind with_usb with_v4l2 +with_vosk with_vulkan with_xcomposite with_xcursor @@ -2450,6 +2452,7 @@ Optional Packages: handling) --without-usb do not use the libusb library --without-v4l2 do not use v4l2 (video capture) + --without-vosk do not use Vosk --without-vulkan do not use Vulkan --without-xcomposite do not use the Xcomposite extension --without-xcursor do not use the Xcursor extension @@ -4360,6 +4363,13 @@ then : fi
+# Check whether --with-vosk was given. +if test ${with_vosk+y} +then : + withval=$with_vosk; if test "x$withval" = "xno"; then ac_cv_header_vosk_api_h=no; fi +fi + + # Check whether --with-vulkan was given. if test ${with_vulkan+y} then : @@ -8282,6 +8292,12 @@ if test "x$ac_cv_header_valgrind_valgrind_h" = xyes then : printf "%s\n" "#define HAVE_VALGRIND_VALGRIND_H 1" >>confdefs.h
+fi +ac_fn_c_check_header_compile "$LINENO" "vosk_api.h" "ac_cv_header_vosk_api_h" "$ac_includes_default" +if test "x$ac_cv_header_vosk_api_h" = xyes +then : + printf "%s\n" "#define HAVE_VOSK_API_H 1" >>confdefs.h + fi
ac_fn_c_check_header_compile "$LINENO" "sys/mkdev.h" "ac_cv_header_sys_mkdev_h" "$ac_includes_default" @@ -18252,6 +18268,79 @@ then as_fn_append wine_warnings "|No sound system was found. Windows applications will be silent." fi
+if test "$ac_cv_header_vosk_api_h" = "yes" +then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for -lvosk" >&5 +printf %s "checking for -lvosk... " >&6; } +if test ${ac_cv_lib_soname_vosk+y} +then : + printf %s "(cached) " >&6 +else $as_nop + ac_check_soname_save_LIBS=$LIBS +LIBS="-lvosk $LIBS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +char vosk_recognizer_new (); +int +main (void) +{ +return vosk_recognizer_new (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + case "$LIBEXT" in + dll) ac_cv_lib_soname_vosk=`$ac_cv_path_LDD conftest.exe | grep "vosk" | sed -e "s/dll.*/dll/"';2,$d'` ;; + dylib) ac_cv_lib_soname_vosk=`$OTOOL -L conftest$ac_exeext | grep "libvosk\.[0-9A-Za-z.]*dylib" | sed -e "s/^.*/(libvosk.[0-9A-Za-z.]*dylib).*$/\1/"';2,$d'` ;; + *) ac_cv_lib_soname_vosk=`$READELF -d conftest$ac_exeext | grep "NEEDED.*libvosk\.$LIBEXT" | sed -e "s/^.*\[\(libvosk\.$LIBEXT[^ ]*\)\].*$/\1/"';2,$d'` + if ${ac_cv_lib_soname_vosk:+false} : +then : + ac_cv_lib_soname_vosk=`$LDD conftest$ac_exeext | grep "libvosk\.$LIBEXT" | sed -e "s/^.*(libvosk.$LIBEXT[^ ]*).*$/\1/"';2,$d'` +fi ;; + esac +else $as_nop + ac_cv_lib_soname_vosk= +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + LIBS=$ac_check_soname_save_LIBS +fi +if ${ac_cv_lib_soname_vosk:+false} : +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not found" >&5 +printf "%s\n" "not found" >&6; } + +else $as_nop + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_soname_vosk" >&5 +printf "%s\n" "$ac_cv_lib_soname_vosk" >&6; } + +printf "%s\n" "#define SONAME_LIBVOSK "$ac_cv_lib_soname_vosk"" >>confdefs.h + + VOSK_LIBS="-lvosk" + + ac_cv_lib_vosk=yes + +printf "%s\n" "#define HAVE_VOSK 1" >>confdefs.h + +fi +fi +if test "x$ac_cv_lib_vosk" != xyes +then : + case "x$with_vosk" in + x) as_fn_append wine_notices "|libvosk ${notice_platform}development files not found (or too old), Vosk aka speech recognition won't be supported." ;; + xno) ;; + *) as_fn_error $? "libvosk ${notice_platform}development files not found (or too old), Vosk aka speech recognition won't be supported. +This is an error since --with-vosk was requested." "$LINENO" 5 ;; +esac +enable_vosk=${enable_vosk:-no} +fi + if test "x$with_vulkan" != "xno" then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for -lvulkan" >&5 @@ -23121,6 +23210,7 @@ GSSAPI_LIBS = $GSSAPI_LIBS PROCSTAT_LIBS = $PROCSTAT_LIBS NETAPI_CFLAGS = $NETAPI_CFLAGS NETAPI_LIBS = $NETAPI_LIBS +VOSK_LIBS = $VOSK_LIBS MSVCRTFLAGS = $MSVCRTFLAGS DELAYLOADFLAG = $DELAYLOADFLAG WINELOADER_PROGRAMS = $WINELOADER_PROGRAMS diff --git a/include/config.h.in b/include/config.h.in index fe2fc36a914..2858797aa59 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -645,6 +645,12 @@ /* Define to 1 if you have the <valgrind/valgrind.h> header file. */ #undef HAVE_VALGRIND_VALGRIND_H
+/* Define to 1 if Vosk is available */ +#undef HAVE_VOSK + +/* Define to 1 if you have the <vosk_api.h> header file. */ +#undef HAVE_VOSK_API_H + /* Define to 1 if you have the <X11/extensions/shape.h> header file. */ #undef HAVE_X11_EXTENSIONS_SHAPE_H
@@ -789,6 +795,9 @@ /* Define to the soname of the libv4l2 library. */ #undef SONAME_LIBV4L2
+/* Define to the soname of the libvosk library. */ +#undef SONAME_LIBVOSK + /* Define to the soname of the libvulkan library. */ #undef SONAME_LIBVULKAN
From: Bernhard Kölbl besentv@gmail.com
Signed-off-by: Bernhard Kölbl besentv@gmail.com --- dlls/windows.media.speech/Makefile.in | 5 +- dlls/windows.media.speech/main.c | 21 +++++++ dlls/windows.media.speech/private.h | 4 ++ dlls/windows.media.speech/unixlib.h | 38 ++++++++++++ dlls/windows.media.speech/vosk.c | 84 +++++++++++++++++++++++++++ 5 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 dlls/windows.media.speech/unixlib.h create mode 100644 dlls/windows.media.speech/vosk.c
diff --git a/dlls/windows.media.speech/Makefile.in b/dlls/windows.media.speech/Makefile.in index 10903cb1d7b..0e7aef05f35 100644 --- a/dlls/windows.media.speech/Makefile.in +++ b/dlls/windows.media.speech/Makefile.in @@ -1,5 +1,7 @@ MODULE = windows.media.speech.dll +UNIXLIB = windows.media.speech.so IMPORTS = combase uuid +UNIX_LIBS = $(VOSK_LIBS)
C_SRCS = \ async.c \ @@ -8,6 +10,7 @@ C_SRCS = \ main.c \ recognizer.c \ synthesizer.c \ - vector.c + vector.c \ + vosk.c
IDL_SRCS = classes.idl diff --git a/dlls/windows.media.speech/main.c b/dlls/windows.media.speech/main.c index e772a791588..f02cf6c898f 100644 --- a/dlls/windows.media.speech/main.c +++ b/dlls/windows.media.speech/main.c @@ -20,10 +20,31 @@ #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; + + if (reason == DLL_PROCESS_ATTACH) + { + DisableThreadLibraryCalls(instance); + __wine_init_unix_call(); + + if ((status = WINE_UNIX_CALL(unix_vosk_process_attach, NULL))) + { + ERR("Initializing Vosk failed with status %lx.\n", status); + if (status != STATUS_NOT_SUPPORTED) return FALSE; + } + } + + 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.h b/dlls/windows.media.speech/unixlib.h new file mode 100644 index 00000000000..a263f36586c --- /dev/null +++ b/dlls/windows.media.speech/unixlib.h @@ -0,0 +1,38 @@ +/* + * 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_vosk_process_attach, +}; + +#endif diff --git a/dlls/windows.media.speech/vosk.c b/dlls/windows.media.speech/vosk.c new file mode 100644 index 00000000000..70dd495dc9f --- /dev/null +++ b/dlls/windows.media.speech/vosk.c @@ -0,0 +1,84 @@ +/* + * Vosk 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 + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include <stdarg.h> +#include <dlfcn.h> + +#ifdef HAVE_VOSK_API_H +#include <vosk_api.h> +#endif /* HAVE_VOSK_API_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); + +#ifdef SONAME_LIBVOSK + +static void *libvosk_handle; + +static NTSTATUS vosk_process_attach( void *args ) +{ + TRACE("args %p.\n", args); + + if (!(libvosk_handle = dlopen(SONAME_LIBVOSK, RTLD_NOW))) + { + WARN("Failed to load library %s, reason %s.\n", SONAME_LIBVOSK, dlerror()); + return STATUS_DLL_NOT_FOUND; + } + + return STATUS_SUCCESS; +} + +#else /* SONAME_LIBVOSK */ + +#define MAKE_UNSUPPORTED_FUNC( f ) \ + static NTSTATUS f( void *args ) \ + { \ + WARN("wine was compiled without libvosk support. Speech recognition won't work.\n"); \ + return STATUS_NOT_SUPPORTED; \ + } + +MAKE_UNSUPPORTED_FUNC(vosk_process_attach) +#undef MAKE_UNSUPPORTED_FUNC + +#endif /* SONAME_LIBVOSK */ + +unixlib_entry_t __wine_unix_call_funcs[] = +{ + vosk_process_attach, +}; + +unixlib_entry_t __wine_unix_call_wow64_funcs[] = +{ + vosk_process_attach, +};
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/private.h | 2 + dlls/windows.media.speech/recognizer.c | 23 +++ dlls/windows.media.speech/unixlib.h | 17 ++ dlls/windows.media.speech/vosk.c | 242 ++++++++++++++++++++++++- 4 files changed, 283 insertions(+), 1 deletion(-)
diff --git a/dlls/windows.media.speech/private.h b/dlls/windows.media.speech/private.h index 2f804fbf1a7..873c767f814 100644 --- a/dlls/windows.media.speech/private.h +++ b/dlls/windows.media.speech/private.h @@ -47,6 +47,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..e2663658d85 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;
+ vosk_handle vosk_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 vosk_release_params vosk_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);
+ vosk_release_params.vosk_handle = impl->vosk_handle; + WINE_UNIX_CALL(unix_vosk_release, &vosk_release_params); + IVector_ISpeechRecognitionConstraint_Release(impl->constraints); free(impl); } @@ -1083,6 +1093,7 @@ static HRESULT WINAPI recognizer_factory_Create( ISpeechRecognizerFactory *iface { struct recognizer *impl; struct session *session; + struct vosk_create_params vosk_create_params; struct vector_iids constraints_iids = { .iterable = &IID_IIterable_ISpeechRecognitionConstraint, @@ -1125,6 +1136,18 @@ static HRESULT WINAPI recognizer_factory_Create( ISpeechRecognizerFactory *iface if (FAILED(hr = recognizer_factory_create_audio_capture(session))) goto error;
+ if (!(vosk_create_params.locale_len = GetUserDefaultLocaleName(vosk_create_params.locale, LOCALE_NAME_MAX_LENGTH))) + goto error; + + vosk_create_params.sample_rate = (float)session->capture_wfx.nSamplesPerSec; + if (WINE_UNIX_CALL(unix_vosk_create, &vosk_create_params) != STATUS_SUCCESS) + { + hr = SPERR_WINRT_INTERNAL_ERROR; + goto error; + } + + session->vosk_handle = vosk_create_params.vosk_handle; + InitializeCriticalSection(&session->cs); session->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": recognition_session.cs");
diff --git a/dlls/windows.media.speech/unixlib.h b/dlls/windows.media.speech/unixlib.h index a263f36586c..91dc3a4d9af 100644 --- a/dlls/windows.media.speech/unixlib.h +++ b/dlls/windows.media.speech/unixlib.h @@ -30,9 +30,26 @@
#include "wine/unixlib.h"
+typedef UINT64 vosk_handle; + +struct vosk_create_params +{ + vosk_handle vosk_handle; + float sample_rate; + WCHAR locale[LOCALE_NAME_MAX_LENGTH]; + INT locale_len; +}; + +struct vosk_release_params +{ + vosk_handle vosk_handle; +}; + enum unix_funcs { unix_vosk_process_attach, + unix_vosk_create, + unix_vosk_release, };
#endif diff --git a/dlls/windows.media.speech/vosk.c b/dlls/windows.media.speech/vosk.c index 70dd495dc9f..df6efe84f55 100644 --- a/dlls/windows.media.speech/vosk.c +++ b/dlls/windows.media.speech/vosk.c @@ -24,8 +24,16 @@
#include "config.h"
+#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wchar.h> + #include <stdarg.h> +#include <dirent.h> #include <dlfcn.h> +#include <errno.h>
#ifdef HAVE_VOSK_API_H #include <vosk_api.h> @@ -44,6 +52,13 @@ WINE_DEFAULT_DEBUG_CHANNEL(speech);
#ifdef SONAME_LIBVOSK
+#define MAKE_FUNCPTR( f ) static typeof(f) * p_##f; +MAKE_FUNCPTR(vosk_model_new) +MAKE_FUNCPTR(vosk_recognizer_new) +MAKE_FUNCPTR(vosk_model_free) +MAKE_FUNCPTR(vosk_recognizer_free) +#undef MAKE_FUNCPTR + static void *libvosk_handle;
static NTSTATUS vosk_process_attach( void *args ) @@ -56,10 +71,229 @@ static NTSTATUS vosk_process_attach( void *args ) return STATUS_DLL_NOT_FOUND; }
+#define LOAD_FUNCPTR( f ) \ + if(!(p_##f = dlsym(libvosk_handle, #f))) \ + { \ + ERR("Failed to load symbol %s\n", debugstr_a(#f)); \ + goto error; \ + } + LOAD_FUNCPTR(vosk_model_new) + LOAD_FUNCPTR(vosk_recognizer_new) + LOAD_FUNCPTR(vosk_model_free) + LOAD_FUNCPTR(vosk_recognizer_free) +#undef LOAD_FUNCPTR + + return STATUS_SUCCESS; +error: + dlclose(libvosk_handle); + libvosk_handle = NULL; + return STATUS_ENTRYPOINT_NOT_FOUND; +} + +static inline vosk_handle vosk_recognizer_to_handle( VoskRecognizer *recognizer ) +{ + return (vosk_handle)(UINT_PTR)recognizer; +} + +static inline VoskRecognizer *vosk_recognizer_from_handle( vosk_handle handle ) +{ + return (VoskRecognizer *)(UINT_PTR)handle; +} + +static inline void str_to_lower( char *str ) +{ + char *s; + + for (s = str; s && *s; ++s) + *s = tolower(*s); +} + +static NTSTATUS errno_to_status( int err ) +{ + TRACE("errno %d.\n", err); + + switch (err) + { + case EAGAIN: return STATUS_SHARING_VIOLATION; + case EBADF: return STATUS_INVALID_HANDLE; + case EBUSY: return STATUS_DEVICE_BUSY; + case ENOSPC: return STATUS_DISK_FULL; + case EPERM: + case EROFS: + case EACCES: return STATUS_ACCESS_DENIED; + case ENOTDIR: return STATUS_OBJECT_PATH_NOT_FOUND; + case ENOENT: return STATUS_OBJECT_NAME_NOT_FOUND; + case EISDIR: return STATUS_INVALID_DEVICE_REQUEST; + case EMFILE: + case ENFILE: return STATUS_TOO_MANY_OPENED_FILES; + case EINVAL: return STATUS_INVALID_PARAMETER; + case ENOTEMPTY: return STATUS_DIRECTORY_NOT_EMPTY; + case EPIPE: return STATUS_PIPE_DISCONNECTED; + case EIO: return STATUS_DEVICE_NOT_READY; +#ifdef ENOMEDIUM + case ENOMEDIUM: return STATUS_NO_MEDIA_IN_DEVICE; +#endif + case ENXIO: return STATUS_NO_SUCH_DEVICE; + case ENOTTY: + case EOPNOTSUPP:return STATUS_NOT_SUPPORTED; + case ECONNRESET:return STATUS_PIPE_DISCONNECTED; + case EFAULT: return STATUS_ACCESS_VIOLATION; + case ESPIPE: return STATUS_ILLEGAL_FUNCTION; + case ELOOP: return STATUS_REPARSE_POINT_NOT_RESOLVED; +#ifdef ETIME /* Missing on FreeBSD */ + case ETIME: return STATUS_IO_TIMEOUT; +#endif + case ENOEXEC: /* ?? */ + case EEXIST: /* ?? */ + default: + FIXME("Converting errno %d to STATUS_UNSUCCESSFUL\n", err); + return STATUS_UNSUCCESSFUL; + } +} + +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-"; + char lang_region[6], *dir_name, *model_path; + NTSTATUS status = STATUS_UNSUCCESSFUL; + struct dirent *dirent; + size_t len, path_len; + DIR *dir; + + TRACE("path %s, locale %s, model %p.\n", path, debugstr_a(locale), model); + + if (!path || !model || strlen(locale) < 4) + return STATUS_UNSUCCESSFUL; + + lstrcpynA(lang_region, locale, 6); + str_to_lower(lang_region); + + *model = NULL; + path_len = strlen(path); + + if ((dir = opendir(path)) == NULL) + return errno_to_status(errno); + + while ((dirent = readdir(dir))) + { + if (dirent->d_type != DT_DIR) + continue; + + if (!strcmp(dir_name = dirent->d_name, "..")) + continue; + + len = strlen(vosk_model_identifier_small); + if (strncmp(dir_name, vosk_model_identifier_small, len)) + { + len = strlen(vosk_model_identifier); + if (strncmp(dir_name, vosk_model_identifier, len)) + continue; + } + dir_name += len; + + /* First match for lang and region (en_us), then only lang (en). */ + if (strncmp(dir_name, lang_region, 5) && strncmp(dir_name, lang_region, 2)) + continue; + + if(!(model_path = malloc(path_len + 1 /* '/' */ + strlen(dirent->d_name) + 1))) + return STATUS_MEMORY_NOT_ALLOCATED; + + 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 get_model_by_locale( const char *locale, VoskModel **model ) +{ + NTSTATUS status = STATUS_UNSUCCESSFUL; + const char *suffix = NULL; + char *env, *path; + + TRACE("locale %s, model %p.\n", debugstr_a(locale), model); + + if (!locale || !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"; + + if (suffix && (path = malloc(strlen(env) + strlen(suffix) + 1))) + { + sprintf(path, "%s%s", env, suffix); + status = find_model_by_locale_and_path(path, locale, model); + free(path); + } + + return status; +} + +static NTSTATUS vosk_create( void *args ) +{ + struct vosk_create_params *params = args; + char locale[LOCALE_NAME_MAX_LENGTH]; + VoskRecognizer *recognizer = NULL; + VoskModel *model = NULL; + NTSTATUS status; + + TRACE("args %p.\n", args); + + if(!ntdll_wcstoumbs(params->locale, params->locale_len, locale, LOCALE_NAME_MAX_LENGTH, FALSE)) + return STATUS_UNSUCCESSFUL; + + if ((status = get_model_by_locale(locale, &model)) != STATUS_SUCCESS) + { + ERR("No suitable Vosk model was found for locale %s. Speech recognition won't work.\n", debugstr_a(locale)); + return status; + } + + if (!(recognizer = p_vosk_recognizer_new(model, params->sample_rate))) + goto error; + + /* The model is kept alive inside the recognizer, so we can safely free our ref here. */ + p_vosk_model_free(model); + + params->vosk_handle = vosk_recognizer_to_handle(recognizer); + return STATUS_SUCCESS; + +error: + if (model) p_vosk_model_free(model); + return STATUS_UNSUCCESSFUL; +} + +static NTSTATUS vosk_release( void *args ) +{ + struct vosk_release_params *params = args; + + TRACE("args %p.\n", args); + + p_vosk_recognizer_free(vosk_recognizer_from_handle(params->vosk_handle)); + return STATUS_SUCCESS; }
-#else /* SONAME_LIBVOSK */ +#else /* HAVE_VOSK_API_H */
#define MAKE_UNSUPPORTED_FUNC( f ) \ static NTSTATUS f( void *args ) \ @@ -69,6 +303,8 @@ static NTSTATUS vosk_process_attach( void *args ) }
MAKE_UNSUPPORTED_FUNC(vosk_process_attach) +MAKE_UNSUPPORTED_FUNC(vosk_create) +MAKE_UNSUPPORTED_FUNC(vosk_release) #undef MAKE_UNSUPPORTED_FUNC
#endif /* SONAME_LIBVOSK */ @@ -76,9 +312,13 @@ MAKE_UNSUPPORTED_FUNC(vosk_process_attach) unixlib_entry_t __wine_unix_call_funcs[] = { vosk_process_attach, + vosk_create, + vosk_release, };
unixlib_entry_t __wine_unix_call_wow64_funcs[] = { vosk_process_attach, + vosk_create, + vosk_release, };