-- v3: winetts: Add stub dll.
From: Shaun Ren sren@codeweavers.com
--- configure.ac | 14 ++ dlls/winetts/Makefile.in | 9 ++ dlls/winetts/main.c | 123 +++++++++++++++++ dlls/winetts/tts.c | 220 +++++++++++++++++++++++++++++++ dlls/winetts/winetts.spec | 4 + dlls/winetts/winetts_classes.idl | 28 ++++ dlls/winetts/winetts_private.h | 35 +++++ 7 files changed, 433 insertions(+) create mode 100644 dlls/winetts/Makefile.in create mode 100644 dlls/winetts/main.c create mode 100644 dlls/winetts/tts.c create mode 100644 dlls/winetts/winetts.spec create mode 100644 dlls/winetts/winetts_classes.idl create mode 100644 dlls/winetts/winetts_private.h
diff --git a/configure.ac b/configure.ac index 7229e25dbed..9e5d5ae994e 100644 --- a/configure.ac +++ b/configure.ac @@ -51,6 +51,7 @@ AC_ARG_WITH(oss, AS_HELP_STRING([--without-oss],[do not use the OSS sound AC_ARG_WITH(pcap, AS_HELP_STRING([--without-pcap],[do not use the Packet Capture library]), [if test "x$withval" = "xno"; then ac_cv_header_pcap_pcap_h=no; fi]) AC_ARG_WITH(pcsclite, AS_HELP_STRING([--without-pcsclite],[do not use PCSC lite])) +AC_ARG_WITH(piper, AS_HELP_STRING([--without-piper],[do not use the Piper TTS library])) AC_ARG_WITH(pthread, AS_HELP_STRING([--without-pthread],[do not use the pthread library]), [if test "x$withval" = "xno"; then ac_cv_header_pthread_h=no; fi]) AC_ARG_WITH(pulse, AS_HELP_STRING([--without-pulse],[do not use PulseAudio sound support])) @@ -1808,6 +1809,18 @@ fi WINE_NOTICE_WITH(vulkan,[test "x$ac_cv_lib_soname_vulkan" = "x" -a "x$ac_cv_lib_soname_MoltenVK" = "x"], [libvulkan and libMoltenVK ${notice_platform}development files not found, Vulkan won't be supported.])
+dnl **** Check for libpiper **** +if test "x$with_piper" != "xno" +then + WINE_PACKAGE_FLAGS(PIPER,[piper],[-lpiper],,, + [AC_CHECK_HEADER(piper/piper_c.h, + [AC_CHECK_LIB(piper,piperInitialize,[:],[PIPER_LIBS=""],[$PIPER_LIBS])], + [PIPER_LIBS=""])]) +fi +WINE_NOTICE_WITH(piper,[test "$ac_cv_lib_piper_piperInitialize" != "yes"], + [libpiper ${notice_platform}development files not found, winetts won't be supported.], + [enable_winetts]) + dnl **** Check for gcc specific options ****
if test "x${GCC}" = "xyes" @@ -3186,6 +3199,7 @@ WINE_CONFIG_MAKEFILE(dlls/wineoss.drv) WINE_CONFIG_MAKEFILE(dlls/wineps.drv) WINE_CONFIG_MAKEFILE(dlls/wineps16.drv16,enable_win16) WINE_CONFIG_MAKEFILE(dlls/winepulse.drv) +WINE_CONFIG_MAKEFILE(dlls/winetts) WINE_CONFIG_MAKEFILE(dlls/wineusb.sys) WINE_CONFIG_MAKEFILE(dlls/winevulkan) WINE_CONFIG_MAKEFILE(dlls/winewayland.drv) diff --git a/dlls/winetts/Makefile.in b/dlls/winetts/Makefile.in new file mode 100644 index 00000000000..24820210fd0 --- /dev/null +++ b/dlls/winetts/Makefile.in @@ -0,0 +1,9 @@ +MODULE = winetts.dll +IMPORTS = ole32 + +C_SRCS = \ + main.c \ + tts.c + +IDL_SRCS = \ + winetts_classes.idl diff --git a/dlls/winetts/main.c b/dlls/winetts/main.c new file mode 100644 index 00000000000..5f8b33df4d6 --- /dev/null +++ b/dlls/winetts/main.c @@ -0,0 +1,123 @@ +/* Wine TTS main file. + * + * Copyright 2023 Shaun Ren 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 + */ + +#include <stdarg.h> + +#define COBJMACROS + +#include "windef.h" +#include "winbase.h" +#include "initguid.h" +#include "objbase.h" +#include "sapiddk.h" + +#include "wine/debug.h" + +#include "winetts_private.h" + +WINE_DEFAULT_DEBUG_CHANNEL(winetts); + +BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, void *reserved) +{ + if (reason == DLL_PROCESS_ATTACH) + { + DisableThreadLibraryCalls(instance); + __wine_init_unix_call(); + } + return TRUE; +} + +struct class_factory +{ + IClassFactory IClassFactory_iface; + HRESULT (*create_instance)(REFIID iid, void **out); +}; + +static inline struct class_factory *impl_from_IClassFactory(IClassFactory *iface) +{ + return CONTAINING_RECORD(iface, struct class_factory, IClassFactory_iface); +} + +static HRESULT WINAPI class_factory_QueryInterface(IClassFactory *iface, REFIID iid, void **out) +{ + TRACE("(%p, %s, %p).\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || IsEqualGUID(iid, &IID_IClassFactory)) + { + *out = iface; + IClassFactory_AddRef(iface); + return S_OK; + } + + WARN("%s not implemented.\n", debugstr_guid(iid)); + *out = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI class_factory_AddRef(IClassFactory *iface) +{ + return 2; +} + +static ULONG WINAPI class_factory_Release(IClassFactory *iface) +{ + return 1; +} + +static HRESULT WINAPI class_factory_CreateInstance(IClassFactory *iface, IUnknown *outer, REFIID iid, void **obj) +{ + struct class_factory *This = impl_from_IClassFactory(iface); + + TRACE("(%p, %p, %s, %p).\n", iface, outer, debugstr_guid(iid), obj); + + *obj = NULL; + if (outer) return CLASS_E_NOAGGREGATION; + return This->create_instance(iid, obj); +} + +static HRESULT WINAPI class_factory_LockServer(IClassFactory *iface, BOOL lock) +{ + FIXME("(%d): stub.\n", lock); + return S_OK; +} + +static const IClassFactoryVtbl class_factory_vtbl = +{ + class_factory_QueryInterface, + class_factory_AddRef, + class_factory_Release, + class_factory_CreateInstance, + class_factory_LockServer, +}; + +static struct class_factory ttsengine_cf = {{&class_factory_vtbl}, ttsengine_create}; + +HRESULT WINAPI DllGetClassObject(REFCLSID clsid, REFIID iid, void **obj) +{ + IClassFactory *cf; + + TRACE("(%s, %s, %p).\n", debugstr_guid(clsid), debugstr_guid(iid), obj); + + if (IsEqualCLSID(clsid, &CLSID_WineTTSEngine)) + cf = &ttsengine_cf.IClassFactory_iface; + else + return CLASS_E_CLASSNOTAVAILABLE; + + return IClassFactory_QueryInterface(cf, iid, obj); +} diff --git a/dlls/winetts/tts.c b/dlls/winetts/tts.c new file mode 100644 index 00000000000..e9a900bf153 --- /dev/null +++ b/dlls/winetts/tts.c @@ -0,0 +1,220 @@ +/* + * Wine sapi TTS engine implementation. + * + * Copyright 2023 Shaun Ren 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 + */ + +#include <stdarg.h> + +#define COBJMACROS + +#include "windef.h" +#include "winbase.h" +#include "objbase.h" + +#include "sapiddk.h" +#include "sperror.h" + +#include "wine/debug.h" +#include "wine/heap.h" + +WINE_DEFAULT_DEBUG_CHANNEL(winetts); + +struct ttsengine +{ + ISpTTSEngine ISpTTSEngine_iface; + ISpObjectWithToken ISpObjectWithToken_iface; + LONG ref; + + ISpObjectToken *token; +}; + +static inline struct ttsengine *impl_from_ISpTTSEngine(ISpTTSEngine *iface) +{ + return CONTAINING_RECORD(iface, struct ttsengine, ISpTTSEngine_iface); +} + +static inline struct ttsengine *impl_from_ISpObjectWithToken(ISpObjectWithToken *iface) +{ + return CONTAINING_RECORD(iface, struct ttsengine, ISpObjectWithToken_iface); +} + +static HRESULT WINAPI ttsengine_QueryInterface(ISpTTSEngine *iface, REFIID iid, void **obj) +{ + struct ttsengine *This = impl_from_ISpTTSEngine(iface); + + TRACE("(%p)->(%s %p)\n", This, debugstr_guid(iid), obj); + + if (IsEqualIID(iid, &IID_IUnknown) || + IsEqualIID(iid, &IID_ISpTTSEngine)) + { + *obj = &This->ISpTTSEngine_iface; + } + else if (IsEqualIID(iid, &IID_ISpObjectWithToken)) + *obj = &This->ISpObjectWithToken_iface; + else + { + *obj = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown *)*obj); + return S_OK; +} + +static ULONG WINAPI ttsengine_AddRef(ISpTTSEngine *iface) +{ + struct ttsengine *This = impl_from_ISpTTSEngine(iface); + ULONG ref = InterlockedIncrement(&This->ref); + + TRACE("(%p) ref=%lu\n", This, ref); + + return ref; +} + +static ULONG WINAPI ttsengine_Release(ISpTTSEngine *iface) +{ + struct ttsengine *This = impl_from_ISpTTSEngine(iface); + ULONG ref = InterlockedDecrement(&This->ref); + + TRACE("(%p) ref=%lu\n", This, ref); + + if (!ref) + { + if (This->token) ISpObjectToken_Release(This->token); + heap_free(This); + } + + return ref; +} + +static HRESULT WINAPI ttsengine_Speak(ISpTTSEngine *iface, DWORD flags, REFGUID fmtid, + const WAVEFORMATEX *wfx, const SPVTEXTFRAG *frag_list, + ISpTTSEngineSite *site) +{ + FIXME("(%p, %#lx, %s, %p, %p, %p): stub.\n", iface, flags, debugstr_guid(fmtid), wfx, frag_list, site); + + return E_NOTIMPL; +} + +static HRESULT WINAPI ttsengine_GetOutputFormat(ISpTTSEngine *iface, const GUID *fmtid, + const WAVEFORMATEX *wfx, GUID *out_fmtid, + WAVEFORMATEX **out_wfx) +{ + FIXME("(%p, %s, %p, %p, %p): stub.\n", iface, debugstr_guid(fmtid), wfx, out_fmtid, out_wfx); + + return E_NOTIMPL; +} + +static ISpTTSEngineVtbl ttsengine_vtbl = +{ + ttsengine_QueryInterface, + ttsengine_AddRef, + ttsengine_Release, + ttsengine_Speak, + ttsengine_GetOutputFormat, +}; + +static HRESULT WINAPI objwithtoken_QueryInterface(ISpObjectWithToken *iface, REFIID iid, void **obj) +{ + struct ttsengine *This = impl_from_ISpObjectWithToken(iface); + + TRACE("(%p, %s, %p).\n", iface, debugstr_guid(iid), obj); + + return ISpTTSEngine_QueryInterface(&This->ISpTTSEngine_iface, iid, obj); +} + +static ULONG WINAPI objwithtoken_AddRef(ISpObjectWithToken *iface) +{ + struct ttsengine *This = impl_from_ISpObjectWithToken(iface); + + TRACE("(%p).\n", iface); + + return ISpTTSEngine_AddRef(&This->ISpTTSEngine_iface); +} + +static ULONG WINAPI objwithtoken_Release(ISpObjectWithToken *iface) +{ + struct ttsengine *This = impl_from_ISpObjectWithToken(iface); + + TRACE("(%p).\n", iface); + + return ISpTTSEngine_Release(&This->ISpTTSEngine_iface); +} + +static HRESULT WINAPI objwithtoken_SetObjectToken(ISpObjectWithToken *iface, ISpObjectToken *token) +{ + struct ttsengine *This = impl_from_ISpObjectWithToken(iface); + + FIXME("(%p, %p): semi-stub.\n", iface, token); + + if (!token) + return E_INVALIDARG; + if (This->token) + return SPERR_ALREADY_INITIALIZED; + + ISpObjectToken_AddRef(token); + This->token = token; + return S_OK; +} + +static HRESULT WINAPI objwithtoken_GetObjectToken(ISpObjectWithToken *iface, ISpObjectToken **token) +{ + struct ttsengine *This = impl_from_ISpObjectWithToken(iface); + + TRACE("(%p, %p).\n", iface, token); + + if (!token) + return E_POINTER; + + *token = This->token; + if (*token) + { + ISpObjectToken_AddRef(*token); + return S_OK; + } + else + return S_FALSE; +} + +static const ISpObjectWithTokenVtbl objwithtoken_vtbl = +{ + objwithtoken_QueryInterface, + objwithtoken_AddRef, + objwithtoken_Release, + objwithtoken_SetObjectToken, + objwithtoken_GetObjectToken +}; + +HRESULT ttsengine_create(REFIID iid, void **obj) +{ + struct ttsengine *This; + HRESULT hr; + + if (!(This = heap_alloc(sizeof(*This)))) + return E_OUTOFMEMORY; + + This->ISpTTSEngine_iface.lpVtbl = &ttsengine_vtbl; + This->ISpObjectWithToken_iface.lpVtbl = &objwithtoken_vtbl; + This->ref = 1; + + This->token = NULL; + + hr = ISpTTSEngine_QueryInterface(&This->ISpTTSEngine_iface, iid, obj); + ISpTTSEngine_Release(&This->ISpTTSEngine_iface); + return hr; +} diff --git a/dlls/winetts/winetts.spec b/dlls/winetts/winetts.spec new file mode 100644 index 00000000000..b16365d0c9f --- /dev/null +++ b/dlls/winetts/winetts.spec @@ -0,0 +1,4 @@ +@ stdcall -private DllCanUnloadNow() +@ stdcall -private DllGetClassObject(ptr ptr ptr) +@ stdcall -private DllRegisterServer() +@ stdcall -private DllUnregisterServer() diff --git a/dlls/winetts/winetts_classes.idl b/dlls/winetts/winetts_classes.idl new file mode 100644 index 00000000000..8491c10d55c --- /dev/null +++ b/dlls/winetts/winetts_classes.idl @@ -0,0 +1,28 @@ +/* + * Wine TTS classes. + * + * Copyright 2023 Shaun Ren 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 + */ + +#pragma makedep register + +[ + helpstring("Wine TTS Engine"), + threading(both), + uuid(3fba361f-0dec-477c-b02e-57d07623b1bf) +] +coclass WineTTSEngine {} diff --git a/dlls/winetts/winetts_private.h b/dlls/winetts/winetts_private.h new file mode 100644 index 00000000000..eee62ccf8b5 --- /dev/null +++ b/dlls/winetts/winetts_private.h @@ -0,0 +1,35 @@ +/* + * Wine TTS private header file. + * + * Copyright 2023 Shaun Ren 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_WINETTS_PRIVATE_H +#define __WINE_WINETTS_PRIVATE_H + +#include <stdbool.h> +#include <stdint.h> +#include "windef.h" +#include "winternl.h" + +#include "wine/unixlib.h" + +DEFINE_GUID(CLSID_WineTTSEngine, 0x3fba361f,0x0dec,0x477c,0xb0,0x2e,0x57,0xd0,0x76,0x23,0xb1,0xbf); + +HRESULT ttsengine_create(REFIID iid, void **obj); + +#endif /* __WINE_WINETTS_PRIVATE_H */
On Sat Sep 30 18:32:57 2023 +0000, Hans Leidekker wrote:
Did you consider using a Windows build of the library?
Is there a way to integrate windows libraries in Wine? Also I'm not sure whether the Windows verison functions in wine or if its performance is good, since it relies on onnxruntime for inference.
On Sun Oct 1 17:12:57 2023 +0000, Shaun Ren wrote:
Is there a way to integrate windows libraries in Wine? Also I'm not sure whether the Windows verison functions in wine or if its performance is good, since it relies on onnxruntime for inference.
Windows libraries are imported in Wine under libs. I doubt that performance would be a concern. It might actually be the other way around since Unix calls add overhead. Extra dependencies like the one you mention would be a reason not to use the Windows version.
On Sun Oct 1 18:46:34 2023 +0000, Hans Leidekker wrote:
Windows libraries are imported in Wine under libs. I doubt that performance would be a concern. It might actually be the other way around since Unix calls add overhead. Extra dependencies like the one you mention would be a reason not to use the Windows version.
The library itself is in C++, which can't be directly imported under libs if I'm not mistaken. Moreover, regarding onnxruntime, it seems to utilize a lot of hardware acceleration features that I'm not sure if it would function properly under Wine.
On Sun Oct 1 18:51:06 2023 +0000, Shaun Ren wrote:
The library itself is in C++, which can't be directly imported under libs if I'm not mistaken. Moreover, regarding onnxruntime, it seems to utilize a lot of hardware acceleration features that I'm not sure if it would function properly under Wine.
Also, this library has a few extra dependencies that need to be imported as well, including onnxruntime which is quite large.
On Sun Oct 1 18:52:35 2023 +0000, Shaun Ren wrote:
Also, this library has a few extra dependencies that need to be imported as well, including onnxruntime which is quite large.
Right, those are good reasons to go with the Unix library.
How does this relate to e.g. `MSTTSEngine.dll`?
On Tue Oct 10 22:22:20 2023 +0000, Huw Davies wrote:
How does this relate to e.g. `MSTTSEngine.dll`?
A TTS Engine for sapi implements the interface `ISpTTSEngine`. Multiple TTS engines can be registered and are listed under `HKLM\SOFTWARE\Microsoft\Speech\Voices\Tokens` with `CLSID` given as a value. `winetts.dll` here is one such instance, and `MSTTSEngine.dll` would be another.
Usually an application will query for a voice under `Voices\Tokens` that best matches the requirements of the application, and will instantiate the engine using the given `CLSID`.
On Tue Oct 10 22:24:16 2023 +0000, Shaun Ren wrote:
A TTS Engine for sapi implements the interface `ISpTTSEngine`. Multiple TTS engines can be registered and are listed under `HKLM\SOFTWARE\Microsoft\Speech\Voices\Tokens` with `CLSID` given as a value. `winetts.dll` here is one such instance, and `MSTTSEngine.dll` would be another. Usually an application will query for a voice under `Voices\Tokens` that best matches the requirements of the application, and will instantiate the engine using the given `CLSID`.
What I'm getting at is why don't we call this `MSTTSEngine.dll` rather than make up our own name?
On Wed Oct 11 06:44:00 2023 +0000, Huw Davies wrote:
What I'm getting at is why don't we call this `MSTTSEngine.dll` rather than make up our own name?
Since this is a separate implementation of the `ISpTTSEngine`, the implementation is different and data fields under `Voices\Tokens` are different, it makes sense to distinguish it from `MSTTSEngine`. Just like the SAPI implementation of Amazon polly has its own separate dll.
Moreover, in theory `MSTTSEngine.dll` from windows can be installed alongside our own implementation (I haven't tested this).