Second commit is to be removed.
Signed-off-by: Bernhard Kölbl besentv@gmail.com
-- v3: windows.media.speech: Implement Vosk create and release functions in the unixlib. 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 | 20 ++++++ dlls/windows.media.speech/private.h | 4 ++ dlls/windows.media.speech/unixlib.h | 38 +++++++++++ dlls/windows.media.speech/vosk.c | 90 +++++++++++++++++++++++++++ 5 files changed, 156 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..c1fd6812711 100644 --- a/dlls/windows.media.speech/main.c +++ b/dlls/windows.media.speech/main.c @@ -20,10 +20,30 @@ #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)) != STATUS_SUCCESS) + { + WARN("Initializing unixlib failed with status %lx.\n", status); + 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..daf45e01258 --- /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..d3591d9b809 --- /dev/null +++ b/dlls/windows.media.speech/vosk.c @@ -0,0 +1,90 @@ +/* + * 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(vosk); + +#ifdef HAVE_VOSK_API_H + +#ifdef SONAME_LIBVOSK +static void *libvosk_handle; +#endif /* SONAME_LIBVOSK */ + +static NTSTATUS vosk_process_attach( void *args ) +{ + TRACE("args %p.\n", args); + +#ifdef SONAME_LIBVOSK + 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 */ + return STATUS_NOT_SUPPORTED; +#endif /* SONAME_LIBVOSK */ +} + +#else /* HAVE_VOSK_API_H */ + +#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 /* HAVE_VOSK_API_H */ + +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/recognizer.c | 16 ++ dlls/windows.media.speech/unixlib.h | 17 +- dlls/windows.media.speech/vosk.c | 238 ++++++++++++++++++++++++- 3 files changed, 269 insertions(+), 2 deletions(-)
diff --git a/dlls/windows.media.speech/recognizer.c b/dlls/windows.media.speech/recognizer.c index c2f386206b8..f37e13d96cc 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_instance vosk_instance; + 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.instance = impl->vosk_instance; + 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,11 @@ static HRESULT WINAPI recognizer_factory_Create( ISpeechRecognizerFactory *iface if (FAILED(hr = recognizer_factory_create_audio_capture(session))) goto error;
+ vosk_create_params.sample_rate = (float)session->capture_wfx.nSamplesPerSec; + vosk_create_params.instance = &session->vosk_instance; + if (FAILED(hr = HRESULT_FROM_NT(WINE_UNIX_CALL(unix_vosk_create, &vosk_create_params)))) + goto error; + 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 daf45e01258..f0edf8c883c 100644 --- a/dlls/windows.media.speech/unixlib.h +++ b/dlls/windows.media.speech/unixlib.h @@ -30,9 +30,24 @@
#include "wine/unixlib.h"
+typedef UINT64 vosk_instance; + +struct vosk_create_params +{ + vosk_instance *instance; + float sample_rate; +}; + +struct vosk_release_params +{ + vosk_instance instance; +}; + enum unix_funcs { - unix_vosk_process_attach + 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 d3591d9b809..30064ce4894 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> @@ -40,10 +48,17 @@
#include "unixlib.h"
-WINE_DEFAULT_DEBUG_CHANNEL(vosk); +WINE_DEFAULT_DEBUG_CHANNEL(speech);
#ifdef HAVE_VOSK_API_H
+#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 + #ifdef SONAME_LIBVOSK static void *libvosk_handle; #endif /* SONAME_LIBVOSK */ @@ -59,12 +74,227 @@ static NTSTATUS vosk_process_attach( void *args ) return STATUS_DLL_NOT_FOUND; }
+#define LOAD_FUNCPTR( f ) \ + do if((p##f = dlsym(libvosk_handle, #f)) == NULL) \ + { \ + ERR("Failed to load symbol %s\n", #f); \ + goto error; \ + } while(0) + 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; #else /* SONAME_LIBVOSK */ return STATUS_NOT_SUPPORTED; #endif /* SONAME_LIBVOSK */ }
+static inline vosk_instance to_vosk_instance( VoskRecognizer *ptr ) +{ + return (vosk_instance)(UINT_PTR)ptr; +} + +static inline VoskRecognizer *from_vosk_instance( vosk_instance instance ) +{ + return (VoskRecognizer *)(UINT_PTR)instance; +} + +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_lang_and_path(const char *folder, const char *lcid, VoskModel **model) +{ + static const char *vosk_model_identifier_small = "vosk-model-small-"; + static const char *vosk_model_identifier = "vosk-model-"; + char lang[3], lang_region[6], *dir_name, *path; + NTSTATUS status = STATUS_UNSUCCESSFUL; + struct dirent *dirent; + DIR *dir; + + TRACE("folder %s, lcid %s, model %p.\n", folder, debugstr_a(lcid), model); + + if (!folder || !model || strlen(lcid) < 4) + return STATUS_UNSUCCESSFUL; + + lstrcpynA(lang, lcid, 3); + lstrcpynA(lang_region, lcid, 6); + + str_to_lower(lang); + str_to_lower(lang_region); + + *model = NULL; + + if ((dir = opendir(folder)) == 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; + + if (strstr(dir_name, vosk_model_identifier_small)) + dir_name += strlen(vosk_model_identifier_small); + else if (strstr(dir_name, vosk_model_identifier)) + dir_name += strlen(vosk_model_identifier); + + if (strstr(dir_name, lang_region) != dir_name && strstr(dir_name, lang) != dir_name) + continue; + + path = malloc(strlen(folder) + 1 /* '/' */ + strlen(dirent->d_name) + 1); + sprintf(path, "%s/%s", folder, dirent->d_name); + + TRACE("Trying to load Vosk model %s.\n", debugstr_a(path)); + + *model = pvosk_model_new(path); + free(path); + + if (*model) + { + status = STATUS_SUCCESS; + break; + } + } + + closedir(dir); + + return status; +} + +static NTSTATUS get_model_by_lang(const char *lcid, VoskModel **model) +{ + static const char *cache_vosk = "/.cache/vosk"; + static const char *vosk = "/vosk"; + NTSTATUS status = STATUS_UNSUCCESSFUL; + char *path = NULL, *env = NULL; + + TRACE("lcid %s, model %p.\n", debugstr_a(lcid), model); + + if (!lcid || !model) + return STATUS_UNSUCCESSFUL; + + if (!find_model_by_lang_and_path(getenv("VOSK_MODEL_PATH"), lcid, model)) + return STATUS_SUCCESS; + if (!find_model_by_lang_and_path("/usr/share/vosk", lcid, model)) + return STATUS_SUCCESS; + + if ((env = getenv("XDG_CACHE_HOME"))) + { + path = malloc(strlen(env) + strlen(vosk) + 1); + sprintf(path, "%s%s", env, vosk); + + status = find_model_by_lang_and_path(path, lcid, model); + } + else if ((env = getenv("HOME"))) + { + path = malloc(strlen(env) + strlen(cache_vosk) + 1); + sprintf(path, "%s%s", env, cache_vosk); + + status = find_model_by_lang_and_path(path, lcid, model); + } + + if (path) + free(path); + + return status; +} + +static NTSTATUS vosk_create( void *args ) +{ + struct vosk_create_params *params = args; + VoskRecognizer *recognizer = NULL; + VoskModel *model = NULL; + NTSTATUS status; + + TRACE("args %p.\n", args); + + if ((status = get_model_by_lang(getenv("LC_NAME"), &model)) != STATUS_SUCCESS) + return status; + + if (!(recognizer = pvosk_recognizer_new(model, params->sample_rate))) + goto error; + + /* The model is kept alive inside the recognizer, so we can safely free our ref here. */ + pvosk_model_free(model); + + *params->instance = to_vosk_instance(recognizer); + return STATUS_SUCCESS; + +error: + if (model) pvosk_model_free(model); + *params->instance = to_vosk_instance( NULL ); + return STATUS_UNSUCCESSFUL; +} + +static NTSTATUS vosk_release(void *args) +{ + struct vosk_release_params *params = args; + + TRACE("args %p.\n", args); + + if (!params->instance) + return STATUS_UNSUCCESSFUL; + + pvosk_recognizer_free(from_vosk_instance(params->instance)); + + return STATUS_SUCCESS; +} + #else /* HAVE_VOSK_API_H */
#define MAKE_UNSUPPORTED_FUNC( f ) \ @@ -75,6 +305,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 /* HAVE_VOSK_API_H */ @@ -82,9 +314,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, };
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=129020
Your paranoid android.
=== debian11 (32 bit report) ===
windows.media.speech: speech.c:711: Test failed: RoGetActivationFactory failed, hr 0x8007045a. Unhandled exception: page fault on read access to 0x00000000 in 32-bit code (0x00405df8).
=== debian11 (32 bit zh:CN report) ===
windows.media.speech: speech.c:711: Test failed: RoGetActivationFactory failed, hr 0x8007045a. Unhandled exception: page fault on read access to 0x00000000 in 32-bit code (0x00405df8).
=== debian11b (64 bit WoW report) ===
windows.media.speech: speech.c:711: Test failed: RoGetActivationFactory failed, hr 0x8007045a. Unhandled exception: page fault on read access to 0x0000000000000000 in 64-bit code (0x000000004052a7).
Didn't think about the bot and CI image not having Vosk installed... I'll work around it.
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/vosk.c:
+#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "winerror.h" +#include "winternl.h"
+#include "wine/debug.h"
+#include "unixlib.h"
+WINE_DEFAULT_DEBUG_CHANNEL(vosk);
+#ifdef HAVE_VOSK_API_H
+#ifdef SONAME_LIBVOSK +static void *libvosk_handle; +#endif /* SONAME_LIBVOSK */
I think you should guard these with `SONAME_LIBVOSK`, instead of `HAVE_VOSK_API_H`. It would save you the `#ifdef` below in vosk_process_attach.
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/unixlib.h:
#include "wine/unixlib.h"
+typedef UINT64 vosk_instance;
+struct vosk_create_params +{
- vosk_instance *instance;
You probably don't want to use a pointer if you can avoid it here, and the `vosk_create` unixlib entry point should write the `param.instance` value, which you would read back on the PE side.
Using pointers in unixlib parameters requires to implement WOW64 entry points to convert 32-bit sized pointers to 64-bit.
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/unixlib.h:
+typedef UINT64 vosk_instance;
+struct vosk_create_params +{
- vosk_instance *instance;
- float sample_rate;
+};
+struct vosk_release_params +{
- vosk_instance instance;
+};
enum unix_funcs {
- unix_vosk_process_attach
Probably you could've added the comma before.
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/vosk.c:
#include "unixlib.h"
-WINE_DEFAULT_DEBUG_CHANNEL(vosk);
There too, maybe the change should be moved earlier.
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/vosk.c:
return STATUS_DLL_NOT_FOUND; }
+#define LOAD_FUNCPTR( f ) \
- do if((p##f = dlsym(libvosk_handle, #f)) == NULL) \
- { \
ERR("Failed to load symbol %s\n", #f); \
goto error; \
- } while(0)
You don't need the `do while (0)` for this macro. Also maybe `!` instead of `== NULL`?
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/vosk.c:
- libvosk_handle = NULL;
- return STATUS_ENTRYPOINT_NOT_FOUND;
#else /* SONAME_LIBVOSK */ return STATUS_NOT_SUPPORTED; #endif /* SONAME_LIBVOSK */ }
+static inline vosk_instance to_vosk_instance( VoskRecognizer *ptr ) +{
- return (vosk_instance)(UINT_PTR)ptr;
+}
+static inline VoskRecognizer *from_vosk_instance( vosk_instance instance ) +{
- return (VoskRecognizer *)(UINT_PTR)instance;
+}
I think usually we name these helpers with the type being converted too, though i don't really mind.
As a suggestion, if you're only going to have a single unixlib handle type, and because we generally use the `handle` term elsewhere, maybe you could name `vosk_instance` to `vosk_handle` and name these `vosk_recognizer_(from|to)_handle`?
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/vosk.c:
- return STATUS_SUCCESS;
+error:
- if (model) pvosk_model_free(model);
- *params->instance = to_vosk_instance( NULL );
- return STATUS_UNSUCCESSFUL;
+}
+static NTSTATUS vosk_release(void *args) +{
- struct vosk_release_params *params = args;
- TRACE("args %p.\n", args);
- if (!params->instance)
return STATUS_UNSUCCESSFUL;
Can this ever happen?
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/vosk.c:
- if ((status = get_model_by_lang(getenv("LC_NAME"), &model)) != STATUS_SUCCESS)
return status;
- if (!(recognizer = pvosk_recognizer_new(model, params->sample_rate)))
goto error;
- /* The model is kept alive inside the recognizer, so we can safely free our ref here. */
- pvosk_model_free(model);
- *params->instance = to_vosk_instance(recognizer);
- return STATUS_SUCCESS;
+error:
- if (model) pvosk_model_free(model);
- *params->instance = to_vosk_instance( NULL );
I don't think you have or even should set the instance on error. Especially as the client ignores the value in that case.
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/vosk.c:
#include "unixlib.h"
-WINE_DEFAULT_DEBUG_CHANNEL(vosk); +WINE_DEFAULT_DEBUG_CHANNEL(speech);
#ifdef HAVE_VOSK_API_H
+#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
I think we usually prefix these pointers with `p_` when the original functions use snake case, so that it's a bit more readable than `pvosk`.
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/vosk.c:
free(path);
- return status;
+}
+static NTSTATUS vosk_create( void *args ) +{
- struct vosk_create_params *params = args;
- VoskRecognizer *recognizer = NULL;
- VoskModel *model = NULL;
- NTSTATUS status;
- TRACE("args %p.\n", args);
- if ((status = get_model_by_lang(getenv("LC_NAME"), &model)) != STATUS_SUCCESS)
return status;
I suspect we might want to use the locale taken from the Windows user/system locale instead? Also I think `LC_NAME` doesn't and isn't usually set.
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/vosk.c:
return STATUS_SUCCESS;
- if ((env = getenv("XDG_CACHE_HOME")))
- {
path = malloc(strlen(env) + strlen(vosk) + 1);
sprintf(path, "%s%s", env, vosk);
status = find_model_by_lang_and_path(path, lcid, model);
- }
- else if ((env = getenv("HOME")))
- {
path = malloc(strlen(env) + strlen(cache_vosk) + 1);
sprintf(path, "%s%s", env, cache_vosk);
status = find_model_by_lang_and_path(path, lcid, model);
- }
Pretty sure you want the lookup order to be `VOSK_MODEL_PATH` > `~/.cache/vosk` > `/usr/share`?
I think the XDG_CACHE_HOME / HOME could be factored together a bit more.
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/vosk.c:
- {
path = malloc(strlen(env) + strlen(vosk) + 1);
sprintf(path, "%s%s", env, vosk);
status = find_model_by_lang_and_path(path, lcid, model);
- }
- else if ((env = getenv("HOME")))
- {
path = malloc(strlen(env) + strlen(cache_vosk) + 1);
sprintf(path, "%s%s", env, cache_vosk);
status = find_model_by_lang_and_path(path, lcid, model);
- }
- if (path)
free(path);
FWIW `free(NULL)` is fine.
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/vosk.c:
- {
if (dirent->d_type != DT_DIR)
continue;
if (!strcmp(dir_name = dirent->d_name, ".."))
continue;
if (strstr(dir_name, vosk_model_identifier_small))
dir_name += strlen(vosk_model_identifier_small);
else if (strstr(dir_name, vosk_model_identifier))
dir_name += strlen(vosk_model_identifier);
if (strstr(dir_name, lang_region) != dir_name && strstr(dir_name, lang) != dir_name)
continue;
path = malloc(strlen(folder) + 1 /* '/' */ + strlen(dirent->d_name) + 1);
You can probably save at least one `strlen`. Also you don't check for malloc failures.
Rémi Bernon (@rbernon) commented about dlls/windows.media.speech/vosk.c:
if (strstr(dir_name, vosk_model_identifier_small))
dir_name += strlen(vosk_model_identifier_small);
else if (strstr(dir_name, vosk_model_identifier))
dir_name += strlen(vosk_model_identifier);
if (strstr(dir_name, lang_region) != dir_name && strstr(dir_name, lang) != dir_name)
continue;
path = malloc(strlen(folder) + 1 /* '/' */ + strlen(dirent->d_name) + 1);
sprintf(path, "%s/%s", folder, dirent->d_name);
TRACE("Trying to load Vosk model %s.\n", debugstr_a(path));
*model = pvosk_model_new(path);
free(path);
This feels a bit too fuzzy. 1) Do we need to iterate over the directory? 2) Is strstr really what we want here?
FWIW I think you don't need the second commit at all.
On Thu Feb 2 10:08:03 2023 +0000, Rémi Bernon wrote:
You probably don't want to use a pointer if you can avoid it here, and the `vosk_create` unixlib entry point should write the `param.instance` value, which you would read back on the PE side. Using pointers in unixlib parameters requires to implement WOW64 entry points to convert 32-bit sized pointers to 64-bit.
Yeah, it actually isn't supposed to be a pointer. Idk why I put the asterisk there. I think we talked about this a while ago, where we said its safe to pass references/handles via a UINT64 value.
On Thu Feb 2 10:08:05 2023 +0000, Rémi Bernon wrote:
I think usually we name these helpers with the type being converted too, though i don't really mind. As a suggestion, if you're only going to have a single unixlib handle type, and because we generally use the `handle` term elsewhere, maybe you could name `vosk_instance` to `vosk_handle` and name these `vosk_recognizer_(from|to)_handle`?
Sure.
On Thu Feb 2 10:08:06 2023 +0000, Rémi Bernon wrote:
I suspect we might want to use the locale taken from the Windows user/system locale instead? Also I think `LC_NAME` doesn't and isn't usually set.
How do I access that locale?
On Thu Feb 2 10:08:06 2023 +0000, Rémi Bernon wrote:
Pretty sure you want the lookup order to be `VOSK_MODEL_PATH` > `~/.cache/vosk` > `/usr/share`? I think the XDG_CACHE_HOME / HOME could be factored together a bit more.
I took the load order from https://github.com/alphacep/vosk-api/blob/master/python/vosk/__init__.py#L18 . `VOSK_MODEL_PATH` > `/usr/share` > `~/.cache/vosk`
Maybe `if ((env = getenv("XDG_CACHE_HOME")) || (env = getenv("HOME")))` with same body?
On Thu Feb 2 10:08:06 2023 +0000, Rémi Bernon wrote:
This feels a bit too fuzzy. 1) Do we need to iterate over the directory? 2) Is strstr really what we want here?
1) Yes, the Vosk directory contains subdirectories, which are the actual models. 2) strstr was a simple solution to fuzzy search for the correct model name. The python script from earlier does regex matching on the folders mentioned in 1).
On Thu Feb 2 12:24:18 2023 +0000, Bernhard Kölbl wrote:
How do I access that locale?
Probably something with `GetUserDefaultLCID` / `GetUserDefaultLocaleName`, but I think that's the default one and the current selected LCID on Windows is actually returned in the lower WORD of `GetKeyboardLayout`. Probably using the default would be enough for now.
On Thu Feb 2 12:28:59 2023 +0000, Bernhard Kölbl wrote:
I took the load order from https://github.com/alphacep/vosk-api/blob/master/python/vosk/__init__.py#L18 . `VOSK_MODEL_PATH` > `/usr/share` > `~/.cache/vosk` Maybe `if ((env = getenv("XDG_CACHE_HOME")) || (env = getenv("HOME")))` with same body?
Weird. Yeah something like that, but the suffix is also different. I don't know, maybe it's fine like it is.
On Thu Feb 2 12:31:11 2023 +0000, Bernhard Kölbl wrote:
- Yes, the Vosk directory contains subdirectories, which are the actual models.
- strstr was a simple solution to fuzzy search for the correct model
name. The python script from earlier does regex matching on the folders mentioned in 1).
I think the python version only looks for files starting with the pattern, not including it. So `strncmp` with the two possible formats would do that.
On Thu Feb 2 10:08:05 2023 +0000, Rémi Bernon wrote:
Can this ever happen?
Maybe with some mistake on PE, I can just remove it if you want.
On Thu Feb 2 10:08:07 2023 +0000, Rémi Bernon wrote:
You can probably save at least one `strlen`. Also you don't check for malloc failures.
Mind sharing how I could save a strlen?
On Fri Feb 3 20:28:10 2023 +0000, Bernhard Kölbl wrote:
Mind sharing how I could save a strlen?
`folder` doesn't change, does it? You're calling `strlen` to get its length on every iteration.
On Fri Feb 3 21:01:34 2023 +0000, Rémi Bernon wrote:
`folder` doesn't change, does it? You're calling `strlen` to get its length on every iteration.
oof, yeah I didn't think about this at all
Mohamad Al-Jaf (@maljaf) commented about dlls/windows.media.speech/vosk.c:
- static const char *vosk_model_identifier = "vosk-model-";
- char lang[3], lang_region[6], *dir_name, *path;
- NTSTATUS status = STATUS_UNSUCCESSFUL;
- struct dirent *dirent;
- DIR *dir;
- TRACE("folder %s, lcid %s, model %p.\n", folder, debugstr_a(lcid), model);
- if (!folder || !model || strlen(lcid) < 4)
return STATUS_UNSUCCESSFUL;
- lstrcpynA(lang, lcid, 3);
- lstrcpynA(lang_region, lcid, 6);
- str_to_lower(lang);
- str_to_lower(lang_region);
I think you can use CharLowerA here instead.
On Sun Feb 5 09:37:09 2023 +0000, Mohamad Al-Jaf wrote:
I think you can use CharLowerA here instead.
That doesn't work, as I'm on the Unix side here.