This series adds a couple of features to WinRT's media.speech: - IVoiceInformation, and synthetizer's options are present (mainly use to store information, not yet to make fully use of it) - add a dummy implementation (1 single voice); that should be extended in future series by using ISpVoice instead - add a couple of more tests to go with implementation
(the serie should prevent MS Flight simulator to crash on first connection. Crash is due to exception handling of C# generated exceptions from E_NOTIMPL with some X11 related critical resources).
A+
-- v3: windows.media.speech: Implement get/put voice on synthesizer. windows.media.speech: Select a default voice in synthesizer. windows.media.speech: Add more tests about default voice. windows.media.speech: Finish implementation of voice information view. windows.media.speech/tests: Add tests about vector view's content. windows.media.speech: Add basic implementation of IVoiceInformation. windows.media.speech: Add more tests about IVoiceInformation. windows.media.speech: Add basic implementation on synthesizer options. windows.media.speech: Add a couple of synthesizer's options tests.
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/tests/speech.c | 43 +++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-)
diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index fb0fa9e3b4d..2e20d5d3fef 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -1033,16 +1033,57 @@ static void test_SpeechSynthesizer(void)
if (hr == S_OK) { + ISpeechSynthesizerOptions2 *options2; ISpeechSynthesizerOptions3 *options3; + boolean bool_value; + DOUBLE double_value; + enum SpeechAppendedSilence silence_value; + enum SpeechPunctuationSilence punctuation_value;
check_interface(options, &IID_IAgileObject, TRUE); - check_optional_interface(options, &IID_ISpeechSynthesizerOptions2, TRUE); /* Requires Win10 >= 1709 */ + bool_value = 0xff; + hr = ISpeechSynthesizerOptions_get_IncludeSentenceBoundaryMetadata(options, &bool_value); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(! bool_value, "Got unepected option %u\n", bool_value); + bool_value = 0xff; + hr = ISpeechSynthesizerOptions_get_IncludeWordBoundaryMetadata(options, &bool_value); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(!bool_value, "Got unepected option %u\n", bool_value); + + hr = ISpeechSynthesizerOptions_QueryInterface(options, &IID_ISpeechSynthesizerOptions2, (void **)&options2); + ok(hr == S_OK || broken(hr == E_NOINTERFACE), "Got unexpected hr %#lx.\n", hr); /* Requires Win10 >= 1709 */ + + if (hr == S_OK) + { + hr = ISpeechSynthesizerOptions2_get_AudioPitch(options2, &double_value); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(double_value == 1.0f, "Got unepected option %f\n", double_value); + + hr = ISpeechSynthesizerOptions2_get_AudioVolume(options2, &double_value); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(double_value == 1.0f, "Got unepected option %f\n", double_value); + + hr = ISpeechSynthesizerOptions2_get_SpeakingRate(options2, &double_value); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(double_value == 1.0f, "Got unepected option %f\n", double_value); + + ref = ISpeechSynthesizerOptions2_Release(options2); + ok(ref == 2, "Got unexpected ref %lu.\n", ref); + }
hr = ISpeechSynthesizerOptions_QueryInterface(options, &IID_ISpeechSynthesizerOptions3, (void **)&options3); ok(hr == S_OK || broken(hr == E_NOINTERFACE), "Got unexpected hr %#lx.\n", hr); /* Requires Win10 >= 1803 */
if (hr == S_OK) { + hr = ISpeechSynthesizerOptions3_get_AppendedSilence(options3, &silence_value); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(silence_value == SpeechAppendedSilence_Default, "Got unepected option %u\n", silence_value); + + hr = ISpeechSynthesizerOptions3_get_PunctuationSilence(options3, &punctuation_value); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(punctuation_value == SpeechPunctuationSilence_Default, "Got unepected option %u\n", punctuation_value); + ref = ISpeechSynthesizerOptions3_Release(options3); ok(ref == 2, "Got unexpected ref %lu.\n", ref); }
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/synthesizer.c | 327 ++++++++++++++++++++++- dlls/windows.media.speech/tests/speech.c | 2 +- 2 files changed, 325 insertions(+), 4 deletions(-)
diff --git a/dlls/windows.media.speech/synthesizer.c b/dlls/windows.media.speech/synthesizer.c index 39d14b84ab7..0da7dbb64b9 100644 --- a/dlls/windows.media.speech/synthesizer.c +++ b/dlls/windows.media.speech/synthesizer.c @@ -280,6 +280,311 @@ error: return hr; }
+/* + * + * SpeechSynthesizerOptions runtimeclass + * + */ +struct synthesizer_options +{ + ISpeechSynthesizerOptions ISpeechSynthesizerOptions_iface; + ISpeechSynthesizerOptions2 ISpeechSynthesizerOptions2_iface; + ISpeechSynthesizerOptions3 ISpeechSynthesizerOptions3_iface; + LONG ref; + + /* options */ + boolean include_word_boundary; + boolean include_sentence_boundary; + + /* options 2 */ + double audio_volume; + double speaking_rate; + double audio_pitch; + + /* options 3 */ + enum SpeechAppendedSilence appended_silence; + enum SpeechPunctuationSilence punctuation_silence; +}; + +static inline struct synthesizer_options *impl_from_ISpeechSynthesizerOptions( ISpeechSynthesizerOptions *iface ) +{ + return CONTAINING_RECORD(iface, struct synthesizer_options, ISpeechSynthesizerOptions_iface); +} + +static HRESULT WINAPI synthesizer_options_QueryInterface( ISpeechSynthesizerOptions *iface, REFIID iid, void **out) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions(iface); + + TRACE("iface %p, iid %s, out %p stub!\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || + IsEqualGUID(iid, &IID_IInspectable) || + IsEqualGUID(iid, &IID_IAgileObject) || + IsEqualGUID(iid, &IID_ISpeechSynthesizerOptions)) + { + IInspectable_AddRef((*out = &impl->ISpeechSynthesizerOptions_iface)); + return S_OK; + } + + if (IsEqualGUID(iid, &IID_ISpeechSynthesizerOptions2)) + { + IInspectable_AddRef((*out = &impl->ISpeechSynthesizerOptions2_iface)); + return S_OK; + } + + if (IsEqualGUID(iid, &IID_ISpeechSynthesizerOptions3)) + { + IInspectable_AddRef((*out = &impl->ISpeechSynthesizerOptions3_iface)); + return S_OK; + } + + FIXME("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + *out = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI synthesizer_options_AddRef( ISpeechSynthesizerOptions *iface ) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions(iface); + ULONG ref = InterlockedIncrement(&impl->ref); + + TRACE("iface %p, ref %lu.\n", iface, ref); + return ref; +} + +static ULONG WINAPI synthesizer_options_Release( ISpeechSynthesizerOptions *iface ) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions(iface); + ULONG ref = InterlockedDecrement(&impl->ref); + + TRACE("iface %p, ref %lu.\n", iface, ref); + if (ref == 0) + free(impl); + return ref; +} + +static HRESULT WINAPI synthesizer_options_GetIids( ISpeechSynthesizerOptions *iface, ULONG *iid_count, IID **iids ) +{ + FIXME("iface %p, iid_count %p, iids %p stub.\n", iface, iid_count, iids); + return E_NOTIMPL; +} + +static HRESULT WINAPI synthesizer_options_GetRuntimeClassName( ISpeechSynthesizerOptions *iface, HSTRING *class_name ) +{ + FIXME("iface %p, class_name %p stub.\n", iface, class_name); + return E_NOTIMPL; +} + +static HRESULT WINAPI synthesizer_options_GetTrustLevel( ISpeechSynthesizerOptions *iface, TrustLevel *trust_level ) +{ + FIXME("iface %p, trust_level %p stub.\n", iface, trust_level); + return E_NOTIMPL; +} + +static HRESULT WINAPI synthesizer_options_get_IncludeWordBoundaryMetadata( ISpeechSynthesizerOptions *iface, boolean *value ) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions(iface); + TRACE("iface %p, value %p semi-stub.\n", iface, value); + + *value = impl->include_word_boundary; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options_put_IncludeWordBoundaryMetadata( ISpeechSynthesizerOptions *iface, boolean value ) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions(iface); + TRACE("iface %p, value %s semi-stub.\n", iface, value ? "true" : "false"); + + impl->include_word_boundary = value; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options_get_IncludeSentenceBoundaryMetadata( ISpeechSynthesizerOptions *iface, boolean *value ) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions(iface); + TRACE("iface %p, value %p stub.\n", iface, value); + + *value = impl->include_sentence_boundary; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options_put_IncludeSentenceBoundaryMetadata( ISpeechSynthesizerOptions *iface, boolean value ) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions(iface); + TRACE("iface %p, value %s stub.\n", iface, value ? "true" : "false"); + + impl->include_sentence_boundary = value; + return S_OK; +} + +static const struct ISpeechSynthesizerOptionsVtbl synthesizer_options_vtbl = +{ + /*** IUnknown methods ***/ + synthesizer_options_QueryInterface, + synthesizer_options_AddRef, + synthesizer_options_Release, + /*** IInspectable methods ***/ + synthesizer_options_GetIids, + synthesizer_options_GetRuntimeClassName, + synthesizer_options_GetTrustLevel, + /*** ISpeechSynthesizerOptions methods ***/ + synthesizer_options_get_IncludeWordBoundaryMetadata, + synthesizer_options_put_IncludeWordBoundaryMetadata, + synthesizer_options_get_IncludeSentenceBoundaryMetadata, + synthesizer_options_put_IncludeSentenceBoundaryMetadata, +}; + +DEFINE_IINSPECTABLE(synthesizer_options2, ISpeechSynthesizerOptions2, struct synthesizer_options, ISpeechSynthesizerOptions_iface) + +static HRESULT WINAPI synthesizer_options2_get_AudioVolume( ISpeechSynthesizerOptions2 *iface, DOUBLE *value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions2(iface); + + TRACE("iface %p value %p semi-stub!\n", iface, value); + *value = impl->audio_volume; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options2_put_AudioVolume( ISpeechSynthesizerOptions2 *iface, DOUBLE value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions2(iface); + + TRACE("iface %p value %g semi-stub!\n", iface, value); + impl->audio_volume = value; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options2_get_SpeakingRate( ISpeechSynthesizerOptions2 *iface, DOUBLE *value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions2(iface); + + TRACE("iface %p value %p semi-stub!\n", iface, value); + *value = impl->speaking_rate; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options2_put_SpeakingRate( ISpeechSynthesizerOptions2 *iface, DOUBLE value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions2(iface); + + TRACE("iface %p value %g semi-stub!\n", iface, value); + impl->speaking_rate = value; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options2_get_AudioPitch( ISpeechSynthesizerOptions2 *iface, DOUBLE *value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions2(iface); + + TRACE("iface %p value %p semi-stub!\n", iface, value); + *value = impl->audio_pitch; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options2_put_AudioPitch( ISpeechSynthesizerOptions2 *iface, DOUBLE value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions2(iface); + + TRACE("iface %p value %g semi-stub!\n", iface, value); + impl->audio_pitch = value; + return S_OK; +} + +static const struct ISpeechSynthesizerOptions2Vtbl synthesizer_options2_vtbl = +{ + /*** IUnknown methods ***/ + synthesizer_options2_QueryInterface, + synthesizer_options2_AddRef, + synthesizer_options2_Release, + /*** IInspectable methods ***/ + synthesizer_options2_GetIids, + synthesizer_options2_GetRuntimeClassName, + synthesizer_options2_GetTrustLevel, + /*** ISpeechSynthesizerOptions methods ***/ + synthesizer_options2_get_AudioVolume, + synthesizer_options2_put_AudioVolume, + synthesizer_options2_get_SpeakingRate, + synthesizer_options2_put_SpeakingRate, + synthesizer_options2_get_AudioPitch, + synthesizer_options2_put_AudioPitch, +}; + +DEFINE_IINSPECTABLE(synthesizer_options3, ISpeechSynthesizerOptions3, struct synthesizer_options, ISpeechSynthesizerOptions_iface) + +static HRESULT WINAPI synthesizer_options3_get_AppendedSilence( ISpeechSynthesizerOptions3 *iface, enum SpeechAppendedSilence *value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions3(iface); + + TRACE("iface %p value %p semi-stub!\n", iface, value); + *value = impl->appended_silence; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options3_put_AppendedSilence( ISpeechSynthesizerOptions3 *iface, enum SpeechAppendedSilence value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions3(iface); + + TRACE("iface %p value %u semi-stub!\n", iface, value); + impl->appended_silence = value; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options3_get_PunctuationSilence( ISpeechSynthesizerOptions3 *iface, enum SpeechPunctuationSilence *value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions3(iface); + + TRACE("iface %p value %p semi-stub!\n", iface, value); + *value = impl->punctuation_silence; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options3_put_PunctuationSilence( ISpeechSynthesizerOptions3 *iface, enum SpeechPunctuationSilence value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions3(iface); + + TRACE("iface %p value %u semi-stub!\n", iface, value); + impl->punctuation_silence = value; + return S_OK; +} + +static const struct ISpeechSynthesizerOptions3Vtbl synthesizer_options3_vtbl = +{ + /*** IUnknown methods ***/ + synthesizer_options3_QueryInterface, + synthesizer_options3_AddRef, + synthesizer_options3_Release, + /*** IInspectable methods ***/ + synthesizer_options3_GetIids, + synthesizer_options3_GetRuntimeClassName, + synthesizer_options3_GetTrustLevel, + /*** ISpeechSynthesizerOptions methods ***/ + synthesizer_options3_get_AppendedSilence, + synthesizer_options3_put_AppendedSilence, + synthesizer_options3_get_PunctuationSilence, + synthesizer_options3_put_PunctuationSilence, +}; + +static HRESULT synthesizer_options_create( struct synthesizer_options **out ) +{ + struct synthesizer_options *options; + + if (!(options = calloc(1, sizeof(*options)))) return E_OUTOFMEMORY; + + options->ISpeechSynthesizerOptions_iface.lpVtbl = &synthesizer_options_vtbl; + options->ISpeechSynthesizerOptions2_iface.lpVtbl = &synthesizer_options2_vtbl; + options->ISpeechSynthesizerOptions3_iface.lpVtbl = &synthesizer_options3_vtbl; + /* all other values default to 0 or false */ + options->audio_pitch = 1.0; + options->audio_volume = 1.0; + options->speaking_rate = 1.0; + + *out = options; + + TRACE("Created ISpeechSynthesizerOptions %p.\n", *out); + + return S_OK; +} + /* * * SpeechSynthesizer runtimeclass @@ -292,6 +597,8 @@ struct synthesizer ISpeechSynthesizer2 ISpeechSynthesizer2_iface; IClosable IClosable_iface; LONG ref; + + struct synthesizer_options *options; };
/* @@ -352,7 +659,11 @@ static ULONG WINAPI synthesizer_Release( ISpeechSynthesizer *iface ) TRACE("iface %p, ref %lu.\n", iface, ref);
if (!ref) + { + if (impl->options) + ISpeechSynthesizerOptions_Release(&impl->options->ISpeechSynthesizerOptions_iface); free(impl); + }
return ref; } @@ -440,8 +751,11 @@ DEFINE_IINSPECTABLE(synthesizer2, ISpeechSynthesizer2, struct synthesizer, ISpee
static HRESULT WINAPI synthesizer2_get_Options( ISpeechSynthesizer2 *iface, ISpeechSynthesizerOptions **value ) { - FIXME("iface %p, value %p stub.\n", iface, value); - return E_NOTIMPL; + struct synthesizer *impl = impl_from_ISpeechSynthesizer2(iface); + + WARN("iface %p, value %p semi-stub.\n", iface, value); + ISpeechSynthesizerOptions_AddRef(*value = &impl->options->ISpeechSynthesizerOptions_iface); + return S_OK; }
static const struct ISpeechSynthesizer2Vtbl synthesizer2_vtbl = @@ -573,6 +887,7 @@ static HRESULT WINAPI factory_GetTrustLevel( IActivationFactory *iface, TrustLev static HRESULT WINAPI factory_ActivateInstance( IActivationFactory *iface, IInspectable **instance ) { struct synthesizer *impl; + HRESULT hr;
TRACE("iface %p, instance %p.\n", iface, instance);
@@ -581,7 +896,13 @@ static HRESULT WINAPI factory_ActivateInstance( IActivationFactory *iface, IInsp *instance = NULL; return E_OUTOFMEMORY; } - + if (FAILED(hr = synthesizer_options_create(&impl->options))) + { + free(impl); + *instance = NULL; + return hr; + } + ISpeechSynthesizerOptions_AddRef(&impl->options->ISpeechSynthesizerOptions_iface); impl->ISpeechSynthesizer_iface.lpVtbl = &synthesizer_vtbl; impl->ISpeechSynthesizer2_iface.lpVtbl = &synthesizer2_vtbl; impl->IClosable_iface.lpVtbl = &closable_vtbl; diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index 2e20d5d3fef..5b152ae9abf 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -1029,7 +1029,7 @@ static void test_SpeechSynthesizer(void) ISpeechSynthesizerOptions *options;
hr = ISpeechSynthesizer2_get_Options(synthesizer2, &options); - todo_wine ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
if (hr == S_OK) {
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/tests/speech.c | 73 +++++++++++++++++++++++- 1 file changed, 70 insertions(+), 3 deletions(-)
diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index 5b152ae9abf..9096c69cafc 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -683,6 +683,66 @@ static HRESULT WINAPI iterable_hstring_create_static( struct iterable_hstring *i return S_OK; }
+#define check_comparable_presence(a, b) _check_comparable_presence( __LINE__, (a), (b)) +static void _check_comparable_presence( unsigned line, IVectorView_VoiceInformation *voices, IVoiceInformation *voice) +{ + HSTRING in_display, in_id, in_language; + HSTRING vc_display, vc_id, vc_language; + IVoiceInformation *vc_voice; + enum VoiceGender in_gender, vc_gender; + UINT32 size, idx, found_count = 0; + HRESULT hr; + INT32 cmp; + + hr = IVoiceInformation_get_DisplayName(voice, &in_display); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Id(voice, &in_id); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Language(voice, &in_language); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Gender(voice, &in_gender); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVectorView_VoiceInformation_get_Size(voices, &size); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + for (idx = 0; SUCCEEDED(hr = IVectorView_VoiceInformation_GetAt(voices, idx, &vc_voice)); idx++) + { + hr = IVoiceInformation_get_DisplayName(vc_voice, &vc_display); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Id(vc_voice, &vc_id); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Language(vc_voice, &vc_language); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Gender(vc_voice, &vc_gender); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + trace("%u] %s/%s/%s/%u\n", + idx - 1, debugstr_hstring(vc_display), debugstr_hstring(vc_id), debugstr_hstring(vc_language), vc_gender); + + if (SUCCEEDED(WindowsCompareStringOrdinal(in_display, vc_display, &cmp)) && !cmp && + SUCCEEDED(WindowsCompareStringOrdinal(in_id, vc_id, &cmp)) && !cmp && + SUCCEEDED(WindowsCompareStringOrdinal(in_language, vc_language, &cmp)) && !cmp && + in_gender == vc_gender) + { + found_count++; + } + WindowsDeleteString(vc_display); + WindowsDeleteString(vc_id); + WindowsDeleteString(vc_language); + IVoiceInformation_Release(vc_voice); + } + ok(hr == E_BOUNDS, "Got unexpected hr %#lx.\n", hr); + ok(idx != 0, "Vector view shouldn't be empty!\n"); + ok(idx == size, "Incoherent index/size %u/%u!\n", idx, size); + + ok_(__FILE__, line)(found_count == 1, "Found several (%u) instances of %s/%s/%s/%u\n", + found_count, + debugstr_hstring(in_display), debugstr_hstring(in_id), debugstr_hstring(in_language), in_gender); + + WindowsDeleteString(in_display); + WindowsDeleteString(in_id); + WindowsDeleteString(in_language); +} + static void test_ActivationFactory(void) { static const WCHAR *synthesizer_name = L"Windows.Media.SpeechSynthesis.SpeechSynthesizer"; @@ -807,7 +867,8 @@ static void test_SpeechSynthesizer(void) HMODULE hdll; HSTRING str, str2; HRESULT hr; - UINT32 size; + UINT32 size, idx; + BOOLEAN found; ULONG ref;
hr = RoInitialize(RO_INIT_MULTITHREADED); @@ -901,13 +962,19 @@ static void test_SpeechSynthesizer(void) ok(hr == S_OK, "IVectorView_VoiceInformation_GetMany failed, hr %#lx\n", hr); ok(size == 0, "IVectorView_VoiceInformation_GetMany returned count %u\n", size);
- IVectorView_VoiceInformation_Release(voices); - hr = IInstalledVoicesStatic_get_DefaultVoice(voices_static, &voice); todo_wine ok(hr == S_OK, "IInstalledVoicesStatic_get_DefaultVoice failed, hr %#lx\n", hr);
if (hr == S_OK) { + /* check that VoiceInformation in static vector voice are not shared when exposed to user */ + idx = size; + hr = IVectorView_VoiceInformation_IndexOf(voices, voice, &idx, &found); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(!found, "Shouldn't find default element\n"); + + check_comparable_presence(voices, voice); + IVoiceInformation_get_Description(voice, &str2); trace("SpeechSynthesizer default voice %s.\n", debugstr_hstring(str2));
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/synthesizer.c | 187 ++++++++++++++++++++++++ 1 file changed, 187 insertions(+)
diff --git a/dlls/windows.media.speech/synthesizer.c b/dlls/windows.media.speech/synthesizer.c index 0da7dbb64b9..e0e5e19d2bb 100644 --- a/dlls/windows.media.speech/synthesizer.c +++ b/dlls/windows.media.speech/synthesizer.c @@ -23,6 +23,193 @@
WINE_DEFAULT_DEBUG_CHANNEL(speech);
+/* + * + * IVoiceInformation + * + */ +struct voice_information +{ + IVoiceInformation IVoiceInformation_iface; + LONG ref; + + HSTRING id; + HSTRING display_name; + HSTRING language; + HSTRING description; + VoiceGender gender; +}; + +static inline struct voice_information *impl_from_IVoiceInformation( IVoiceInformation *iface ) +{ + return CONTAINING_RECORD(iface, struct voice_information, IVoiceInformation_iface); +} + +static void voice_information_delete( struct voice_information *voice_info ) +{ + WindowsDeleteString(voice_info->id); + WindowsDeleteString(voice_info->display_name); + WindowsDeleteString(voice_info->language); + WindowsDeleteString(voice_info->description); + free(voice_info); +} + +static HRESULT WINAPI voice_information_QueryInterface( IVoiceInformation *iface, REFIID iid, void **ppvObject) +{ + struct voice_information *impl = impl_from_IVoiceInformation( iface ); + + TRACE("iface %p, riid %s, ppv %p\n", iface, wine_dbgstr_guid(iid), ppvObject); + + if (IsEqualGUID(iid, &IID_IUnknown) || + IsEqualGUID(iid, &IID_IInspectable) || + IsEqualGUID(iid, &IID_IVoiceInformation)) + { + IInspectable_AddRef((*ppvObject = &impl->IVoiceInformation_iface)); + return S_OK; + } + + WARN("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + *ppvObject = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI voice_information_AddRef( IVoiceInformation *iface ) +{ + struct voice_information *impl = impl_from_IVoiceInformation(iface); + ULONG ref = InterlockedIncrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + return ref; +} + +static ULONG WINAPI voice_information_Release( IVoiceInformation *iface ) +{ + struct voice_information *impl = impl_from_IVoiceInformation(iface); + ULONG ref = InterlockedDecrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + /* all voices are (for now) statically allocated in all_voices vector. so don't free them */ + return ref; +} + +static HRESULT WINAPI voice_information_GetIids( IVoiceInformation *iface, ULONG *iid_count, IID **iids ) +{ + FIXME("iface %p, iid_count %p, iids %p stub!\n", iface, iid_count, iids); + return E_NOTIMPL; +} + +static HRESULT WINAPI voice_information_GetRuntimeClassName( IVoiceInformation *iface, HSTRING *class_name ) +{ + FIXME("iface %p, class_name %p stub!\n", iface, class_name); + return E_NOTIMPL; +} + +static HRESULT WINAPI voice_information_GetTrustLevel( IVoiceInformation *iface, TrustLevel *trust_level ) +{ + FIXME("iface %p, trust_level %p stub!\n", iface, trust_level); + return E_NOTIMPL; +} + +static HRESULT WINAPI voice_information_get_DisplayName( IVoiceInformation *iface, HSTRING *value ) +{ + struct voice_information *impl = impl_from_IVoiceInformation(iface); + + TRACE("iface %p, value %p!n", iface, value); + return WindowsDuplicateString(impl->display_name, value); +} + +static HRESULT WINAPI voice_information_get_Id( IVoiceInformation *iface, HSTRING *value ) +{ + struct voice_information *impl = impl_from_IVoiceInformation(iface); + + TRACE("iface %p, value %p\n", iface, value); + return WindowsDuplicateString(impl->id, value); +} + +static HRESULT WINAPI voice_information_get_Language( IVoiceInformation *iface, HSTRING *value ) +{ + struct voice_information *impl = impl_from_IVoiceInformation(iface); + + TRACE("iface %p, value %p\n", iface, value); + return WindowsDuplicateString(impl->language, value); +} + +static HRESULT WINAPI voice_information_get_Description( IVoiceInformation *iface, HSTRING *value ) +{ + struct voice_information *impl = impl_from_IVoiceInformation(iface); + + TRACE("iface %p, value %p\n", iface, value); + return WindowsDuplicateString(impl->description, value); +} + +static HRESULT WINAPI voice_information_get_Gender( IVoiceInformation *iface, VoiceGender *value ) +{ + struct voice_information *impl = impl_from_IVoiceInformation(iface); + + TRACE("iface %p, value %p\n", iface, value); + *value = impl->gender; + return S_OK; +} + +static const struct IVoiceInformationVtbl voice_information_vtbl = +{ + /*** IUnknown methods ***/ + voice_information_QueryInterface, + voice_information_AddRef, + voice_information_Release, + + /*** IInspectable methods ***/ + voice_information_GetIids, + voice_information_GetRuntimeClassName, + voice_information_GetTrustLevel, + + /*** IVoiceInformation methods ***/ + voice_information_get_DisplayName, + voice_information_get_Id, + voice_information_get_Language, + voice_information_get_Description, + voice_information_get_Gender, +}; + +HRESULT voice_information_create(const WCHAR *display_name, const WCHAR *id, const WCHAR *locale, + VoiceGender gender, IVoiceInformation **pvoice) +{ + struct voice_information *voice_info; + WCHAR *description; + HRESULT hr; + size_t len, langlen; + + if (!(voice_info = calloc(1, sizeof(*voice_info)))) return E_OUTOFMEMORY; + + len = wcslen(display_name) + 3; + langlen = GetLocaleInfoEx(locale, LOCALE_SLOCALIZEDDISPLAYNAME, NULL, 0); + description = malloc((len + langlen) * sizeof(WCHAR)); + wcscpy(description, display_name); + wcscat(description, L" - "); + GetLocaleInfoEx(locale, LOCALE_SLOCALIZEDDISPLAYNAME, description + len, langlen); + + hr = WindowsCreateString(display_name, wcslen(display_name), &voice_info->display_name); + if (SUCCEEDED(hr)) + hr = WindowsCreateString(id, wcslen(id), &voice_info->id); + if (SUCCEEDED(hr)) + hr = WindowsCreateString(locale, wcslen(locale), &voice_info->language); + if (SUCCEEDED(hr)) + hr = WindowsCreateString(description, len + langlen - 1, &voice_info->description); + if (SUCCEEDED(hr)) + { + voice_info->gender = gender; + voice_info->IVoiceInformation_iface.lpVtbl = &voice_information_vtbl; + + *pvoice = &voice_info->IVoiceInformation_iface; + + TRACE("Created IVoiceInformation %p.\n", *pvoice); + } + else + { + voice_information_delete(voice_info); + } + free(description); + return hr; +} + /* * * IVectorView_VoiceInformation
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/tests/speech.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index 9096c69cafc..358e214dc26 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -857,7 +857,7 @@ static void test_SpeechSynthesizer(void) IVectorView_VoiceInformation *voices = NULL; IInstalledVoicesStatic *voices_static = NULL; ISpeechSynthesisStream *ss_stream = NULL; - IVoiceInformation *voice; + IVoiceInformation *voice, *voice2; IInspectable *inspectable = NULL, *tmp_inspectable = NULL; IAgileObject *agile_object = NULL, *tmp_agile_object = NULL; ISpeechSynthesizer *synthesizer; @@ -953,6 +953,17 @@ static void test_SpeechSynthesizer(void) ok(hr == S_OK, "IVectorView_VoiceInformation_get_Size voices failed, hr %#lx\n", hr); todo_wine ok(size != 0 && size != 0xdeadbeef, "IVectorView_VoiceInformation_get_Size returned %u\n", size);
+ voice = (IVoiceInformation *)0xdeadbeef; + hr = IVectorView_VoiceInformation_GetAt(voices, 0, &voice); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVectorView_VoiceInformation_GetAt(voices, 0, &voice2); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(voice == voice2, "Didn't get the same object\n"); + ref = IVoiceInformation_Release(voice); + ok(ref == 2, "Unexpected ref count %ld\n", ref); + ref = IVoiceInformation_Release(voice2); + ok(ref == 1, "Unexpected ref count %ld\n", ref); + voice = (IVoiceInformation *)0xdeadbeef; hr = IVectorView_VoiceInformation_GetAt(voices, size, &voice); ok(hr == E_BOUNDS, "IVectorView_VoiceInformation_GetAt failed, hr %#lx\n", hr); @@ -962,6 +973,13 @@ static void test_SpeechSynthesizer(void) ok(hr == S_OK, "IVectorView_VoiceInformation_GetMany failed, hr %#lx\n", hr); ok(size == 0, "IVectorView_VoiceInformation_GetMany returned count %u\n", size);
+ hr = IVectorView_VoiceInformation_GetMany(voices, 0, 1, &voice, &size); + ok(hr == S_OK, "IVectorView_VoiceInformation_GetMany failed, hr %#lx\n", hr); + ok(size == 1, "IVectorView_VoiceInformation_GetMany returned count %u\n", size); + ok(voice == voice2, "Didn't get the same object\n"); + ref = IVoiceInformation_Release(voice); + ok(ref == 1, "Unexpected ref count %ld\n", ref); + hr = IInstalledVoicesStatic_get_DefaultVoice(voices_static, &voice); todo_wine ok(hr == S_OK, "IInstalledVoicesStatic_get_DefaultVoice failed, hr %#lx\n", hr);
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/private.h | 9 +++ dlls/windows.media.speech/synthesizer.c | 95 ++++++++++++++++++++---- dlls/windows.media.speech/tests/speech.c | 2 +- 3 files changed, 91 insertions(+), 15 deletions(-)
diff --git a/dlls/windows.media.speech/private.h b/dlls/windows.media.speech/private.h index e80d73ec1fb..e07e695a671 100644 --- a/dlls/windows.media.speech/private.h +++ b/dlls/windows.media.speech/private.h @@ -126,4 +126,13 @@ HRESULT vector_inspectable_create( const struct vector_iids *iids, IVector_IInsp #define DEFINE_IINSPECTABLE_OUTER( pfx, iface_type, impl_type, outer_iface ) \ DEFINE_IINSPECTABLE_( pfx, iface_type, impl_type, impl_from_##iface_type, iface_type##_iface, impl->outer_iface )
+struct synth_voices_provider +{ + struct IVoiceInformation **voices; + unsigned num_voices; +}; + +HRESULT voice_information_allocate(const WCHAR *display_name, const WCHAR *id, const WCHAR *locale, + VoiceGender gender, IVoiceInformation **pvoice); + #endif diff --git a/dlls/windows.media.speech/synthesizer.c b/dlls/windows.media.speech/synthesizer.c index e0e5e19d2bb..088c65889ad 100644 --- a/dlls/windows.media.speech/synthesizer.c +++ b/dlls/windows.media.speech/synthesizer.c @@ -220,6 +220,8 @@ struct voice_information_vector { IVectorView_VoiceInformation IVectorView_VoiceInformation_iface; LONG ref; + + struct synth_voices_provider provider; };
static inline struct voice_information_vector *impl_from_IVectorView_VoiceInformation( IVectorView_VoiceInformation *iface ) @@ -231,7 +233,7 @@ static HRESULT WINAPI vector_view_voice_information_QueryInterface( IVectorView_ { struct voice_information_vector *impl = impl_from_IVectorView_VoiceInformation(iface);
- TRACE("iface %p, iid %s, out %p stub!\n", iface, debugstr_guid(iid), out); + TRACE("iface %p, iid %s, out %p\n", iface, debugstr_guid(iid), out);
if (IsEqualGUID(iid, &IID_IUnknown) || IsEqualGUID(iid, &IID_IInspectable) || @@ -282,23 +284,39 @@ static HRESULT WINAPI vector_view_voice_information_GetTrustLevel( IVectorView_V
static HRESULT WINAPI vector_view_voice_information_GetAt( IVectorView_VoiceInformation *iface, UINT32 index, IVoiceInformation **value ) { - FIXME("iface %p, index %#x, value %p stub!\n", iface, index, value); - *value = NULL; - return E_BOUNDS; + struct voice_information_vector *impl = impl_from_IVectorView_VoiceInformation(iface); + TRACE("iface %p, index %#x, value %p\n", iface, index, value); + if (index >= impl->provider.num_voices) + { + *value = NULL; + return E_BOUNDS; + } + IVoiceInformation_AddRef( *value = impl->provider.voices[index] ); + return S_OK; }
static HRESULT WINAPI vector_view_voice_information_get_Size( IVectorView_VoiceInformation *iface, UINT32 *value ) { - FIXME("iface %p, value %p stub!\n", iface, value); - *value = 0; + struct voice_information_vector *impl = impl_from_IVectorView_VoiceInformation(iface); + TRACE("iface %p, value %p\n", iface, value); + *value = impl->provider.num_voices; return S_OK; }
static HRESULT WINAPI vector_view_voice_information_IndexOf( IVectorView_VoiceInformation *iface, IVoiceInformation *element, UINT32 *index, BOOLEAN *found ) { - FIXME("iface %p, element %p, index %p, found %p stub!\n", iface, element, index, found); - *index = 0; + struct voice_information_vector *impl = impl_from_IVectorView_VoiceInformation(iface); + int i; + + TRACE("iface %p, element %p, index %p, found %p\n", iface, element, index, found); + for (i = 0; i < impl->provider.num_voices; i++) + if (element == impl->provider.voices[i]) + { + *index = i; + *found = TRUE; + return S_OK; + } *found = FALSE; return S_OK; } @@ -306,8 +324,18 @@ static HRESULT WINAPI vector_view_voice_information_IndexOf( IVectorView_VoiceIn static HRESULT WINAPI vector_view_voice_information_GetMany( IVectorView_VoiceInformation *iface, UINT32 start_index, UINT32 items_size, IVoiceInformation **items, UINT *value ) { - FIXME("iface %p, start_index %#x, items %p, value %p stub!\n", iface, start_index, items, value); - *value = 0; + struct voice_information_vector *impl = impl_from_IVectorView_VoiceInformation(iface); + int i; + + TRACE("iface %p, start_index %#x, items %p, value %p\n", iface, start_index, items, value); + if (start_index >= impl->provider.num_voices) + { + *value = 0; + return S_OK; + } + *value = min(impl->provider.num_voices - start_index, items_size); + for (i = 0; i < *value; i++) + IVoiceInformation_AddRef(items[i] = impl->provider.voices[start_index + i]); return S_OK; }
@@ -330,7 +358,8 @@ static const struct IVectorView_VoiceInformationVtbl vector_view_voice_informati static struct voice_information_vector all_voices = { {&vector_view_voice_information_vtbl}, - 0 + 0, + {}, };
/* @@ -1112,6 +1141,26 @@ static const struct IActivationFactoryVtbl factory_vtbl = factory_ActivateInstance, };
+static HRESULT dummy_provider_init(struct synth_voices_provider *provider) +{ + HRESULT hr; + WCHAR locale[LOCALE_NAME_MAX_LENGTH]; + + if (GetUserDefaultLocaleName(locale, ARRAY_SIZE(locale)) > ARRAY_SIZE(locale)) + return E_OUTOFMEMORY; + + provider->voices = calloc(1, sizeof(all_voices.provider.voices[0])); + if (!provider->voices) return E_OUTOFMEMORY; + hr = voice_information_create(L"Dummy voice", L"--noid--", locale, VoiceGender_Male, &provider->voices[0]); + if (FAILED(hr)) + { + free(provider->voices); + } + else + provider->num_voices = 1; + return hr; +} + /* * * IInstalledVoicesStatic for SpeechSynthesizer runtimeclass @@ -1120,12 +1169,30 @@ static const struct IActivationFactoryVtbl factory_vtbl =
DEFINE_IINSPECTABLE(installed_voices_static, IInstalledVoicesStatic, struct synthesizer_statics, IActivationFactory_iface)
+static CRITICAL_SECTION allvoices_cs; +static CRITICAL_SECTION_DEBUG allvoices_critsect_debug = +{ + 0, 0, &allvoices_cs, + { &allvoices_critsect_debug.ProcessLocksList, &allvoices_critsect_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": allvoices_cs") } +}; +static CRITICAL_SECTION allvoices_cs = { &allvoices_critsect_debug, -1, 0, 0, 0, 0 }; + static HRESULT WINAPI installed_voices_static_get_AllVoices( IInstalledVoicesStatic *iface, IVectorView_VoiceInformation **value ) { + HRESULT hr; + TRACE("iface %p, value %p.\n", iface, value); - *value = &all_voices.IVectorView_VoiceInformation_iface; - IVectorView_VoiceInformation_AddRef(*value); - return S_OK; + + EnterCriticalSection(&allvoices_cs); + if (all_voices.provider.num_voices == 0) + hr = dummy_provider_init(&all_voices.provider); + else + hr = S_OK; + if (SUCCEEDED(hr)) + IVectorView_VoiceInformation_AddRef(*value = &all_voices.IVectorView_VoiceInformation_iface); + LeaveCriticalSection(&allvoices_cs); + return hr; }
static HRESULT WINAPI installed_voices_static_get_DefaultVoice( IInstalledVoicesStatic *iface, IVoiceInformation **value ) diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index 358e214dc26..bd3176709b6 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -951,7 +951,7 @@ static void test_SpeechSynthesizer(void) size = 0xdeadbeef; hr = IVectorView_VoiceInformation_get_Size(voices, &size); ok(hr == S_OK, "IVectorView_VoiceInformation_get_Size voices failed, hr %#lx\n", hr); - todo_wine ok(size != 0 && size != 0xdeadbeef, "IVectorView_VoiceInformation_get_Size returned %u\n", size); + ok(size != 0 && size != 0xdeadbeef, "IVectorView_VoiceInformation_get_Size returned %u\n", size);
voice = (IVoiceInformation *)0xdeadbeef; hr = IVectorView_VoiceInformation_GetAt(voices, 0, &voice);
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/tests/speech.c | 34 +++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-)
diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index bd3176709b6..c9d4593ebf6 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -865,7 +865,7 @@ static void test_SpeechSynthesizer(void) IClosable *closable; struct async_inspectable_handler async_inspectable_handler; HMODULE hdll; - HSTRING str, str2; + HSTRING str, str2, default_voice_id; HRESULT hr; UINT32 size, idx; BOOLEAN found; @@ -993,13 +993,18 @@ static void test_SpeechSynthesizer(void)
check_comparable_presence(voices, voice);
- IVoiceInformation_get_Description(voice, &str2); + hr = IVoiceInformation_get_Description(voice, &str2); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); trace("SpeechSynthesizer default voice %s.\n", debugstr_hstring(str2)); - WindowsDeleteString(str2); + + hr = IVoiceInformation_get_Id(voice, &default_voice_id); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ref = IVoiceInformation_Release(voice); ok(ref == 0, "Got unexpected ref %lu.\n", ref); } + else default_voice_id = NULL;
IInstalledVoicesStatic_Release(voices_static); IAgileObject_Release(agile_object); @@ -1016,6 +1021,29 @@ static void test_SpeechSynthesizer(void) hr = IInspectable_QueryInterface(inspectable, &IID_ISpeechSynthesizer, (void **)&synthesizer); ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
+ hr = ISpeechSynthesizer_get_Voice(synthesizer, &voice); + todo_wine ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + if (default_voice_id) + { + if (hr == S_OK) + { + INT32 cmp; + IVoiceInformation_get_Id(voice, &str); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = WindowsCompareStringOrdinal(str, default_voice_id, &cmp); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + hr = WindowsDeleteString(str); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + IVoiceInformation_Release(voice); + } + + hr = WindowsDeleteString(default_voice_id); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + } + /* Test SynthesizeTextToStreamAsync */ hr = WindowsCreateString(simple_synth_text, wcslen(simple_synth_text), &str); ok(hr == S_OK, "WindowsCreateString failed, hr %#lx\n", hr);
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/synthesizer.c | 51 ++++++++++++++++++-- dlls/windows.media.speech/tests/speech.c | 61 +++++++++++------------- 2 files changed, 74 insertions(+), 38 deletions(-)
diff --git a/dlls/windows.media.speech/synthesizer.c b/dlls/windows.media.speech/synthesizer.c index 088c65889ad..a94447a9c49 100644 --- a/dlls/windows.media.speech/synthesizer.c +++ b/dlls/windows.media.speech/synthesizer.c @@ -38,6 +38,7 @@ struct voice_information HSTRING language; HSTRING description; VoiceGender gender; + BOOL is_static; };
static inline struct voice_information *impl_from_IVoiceInformation( IVoiceInformation *iface ) @@ -86,7 +87,9 @@ static ULONG WINAPI voice_information_Release( IVoiceInformation *iface ) struct voice_information *impl = impl_from_IVoiceInformation(iface); ULONG ref = InterlockedDecrement(&impl->ref); TRACE("iface %p, ref %lu.\n", iface, ref); - /* all voices are (for now) statically allocated in all_voices vector. so don't free them */ + /* only deallocate non static instances */ + if (!ref && !impl->is_static) + voice_information_delete(impl); return ref; }
@@ -196,8 +199,9 @@ HRESULT voice_information_create(const WCHAR *display_name, const WCHAR *id, con if (SUCCEEDED(hr)) { voice_info->gender = gender; + voice_info->is_static = TRUE; voice_info->IVoiceInformation_iface.lpVtbl = &voice_information_vtbl; - + voice_info->ref = 1; *pvoice = &voice_info->IVoiceInformation_iface;
TRACE("Created IVoiceInformation %p.\n", *pvoice); @@ -210,6 +214,35 @@ HRESULT voice_information_create(const WCHAR *display_name, const WCHAR *id, con return hr; }
+HRESULT voice_information_clone(IVoiceInformation *voice, IVoiceInformation **out) +{ + struct voice_information *voice_info; + HRESULT hr; + + voice_info = calloc(1, sizeof(*voice_info)); + if (!voice_info) return E_OUTOFMEMORY; + + hr = IVoiceInformation_get_DisplayName(voice, &voice_info->display_name); + if (SUCCEEDED(hr)) + hr = IVoiceInformation_get_Id(voice, &voice_info->id); + if (SUCCEEDED(hr)) + hr = IVoiceInformation_get_Language(voice, &voice_info->language); + if (SUCCEEDED(hr)) + hr = IVoiceInformation_get_Description(voice, &voice_info->description); + if (SUCCEEDED(hr)) + hr = IVoiceInformation_get_Gender(voice, &voice_info->gender); + if (SUCCEEDED(hr)) + { + voice_info->IVoiceInformation_iface.lpVtbl = &voice_information_vtbl; + voice_info->ref = 1; + *out = &voice_info->IVoiceInformation_iface; + } + else + voice_information_delete(voice_info); + + return hr; +} + /* * * IVectorView_VoiceInformation @@ -1197,8 +1230,18 @@ static HRESULT WINAPI installed_voices_static_get_AllVoices( IInstalledVoicesSta
static HRESULT WINAPI installed_voices_static_get_DefaultVoice( IInstalledVoicesStatic *iface, IVoiceInformation **value ) { - FIXME("iface %p, value %p stub!\n", iface, value); - return E_NOTIMPL; + struct IVoiceInformation *static_voice; + HRESULT hr; + + TRACE("iface %p, value %p\n", iface, value); + + EnterCriticalSection(&allvoices_cs); + hr = IVectorView_VoiceInformation_GetAt(&all_voices.IVectorView_VoiceInformation_iface, 0, &static_voice); + if (SUCCEEDED(hr)) + hr = voice_information_clone(static_voice, value); + LeaveCriticalSection(&allvoices_cs); + + return hr; }
static const struct IInstalledVoicesStaticVtbl installed_voices_static_vtbl = diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index c9d4593ebf6..9c5517adda8 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -981,30 +981,26 @@ static void test_SpeechSynthesizer(void) ok(ref == 1, "Unexpected ref count %ld\n", ref);
hr = IInstalledVoicesStatic_get_DefaultVoice(voices_static, &voice); - todo_wine ok(hr == S_OK, "IInstalledVoicesStatic_get_DefaultVoice failed, hr %#lx\n", hr); + ok(hr == S_OK, "IInstalledVoicesStatic_get_DefaultVoice failed, hr %#lx\n", hr);
- if (hr == S_OK) - { - /* check that VoiceInformation in static vector voice are not shared when exposed to user */ - idx = size; - hr = IVectorView_VoiceInformation_IndexOf(voices, voice, &idx, &found); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - ok(!found, "Shouldn't find default element\n"); + /* check that VoiceInformation in static vector voice are not shared when exposed to user */ + idx = size; + hr = IVectorView_VoiceInformation_IndexOf(voices, voice, &idx, &found); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(!found, "Shouldn't find default element\n");
- check_comparable_presence(voices, voice); + check_comparable_presence(voices, voice);
- hr = IVoiceInformation_get_Description(voice, &str2); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - trace("SpeechSynthesizer default voice %s.\n", debugstr_hstring(str2)); - WindowsDeleteString(str2); + hr = IVoiceInformation_get_Description(voice, &str2); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + trace("SpeechSynthesizer default voice %s.\n", debugstr_hstring(str2)); + WindowsDeleteString(str2);
- hr = IVoiceInformation_get_Id(voice, &default_voice_id); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Id(voice, &default_voice_id); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
- ref = IVoiceInformation_Release(voice); - ok(ref == 0, "Got unexpected ref %lu.\n", ref); - } - else default_voice_id = NULL; + ref = IVoiceInformation_Release(voice); + ok(ref == 0, "Got unexpected ref %lu.\n", ref);
IInstalledVoicesStatic_Release(voices_static); IAgileObject_Release(agile_object); @@ -1024,26 +1020,23 @@ static void test_SpeechSynthesizer(void) hr = ISpeechSynthesizer_get_Voice(synthesizer, &voice); todo_wine ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
- if (default_voice_id) + if (hr == S_OK) { - if (hr == S_OK) - { - INT32 cmp; - IVoiceInformation_get_Id(voice, &str); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - hr = WindowsCompareStringOrdinal(str, default_voice_id, &cmp); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - - hr = WindowsDeleteString(str); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - - IVoiceInformation_Release(voice); - } + INT32 cmp; + IVoiceInformation_get_Id(voice, &str); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = WindowsCompareStringOrdinal(str, default_voice_id, &cmp); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
- hr = WindowsDeleteString(default_voice_id); + hr = WindowsDeleteString(str); ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + IVoiceInformation_Release(voice); }
+ hr = WindowsDeleteString(default_voice_id); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + /* Test SynthesizeTextToStreamAsync */ hr = WindowsCreateString(simple_synth_text, wcslen(simple_synth_text), &str); ok(hr == S_OK, "WindowsCreateString failed, hr %#lx\n", hr);
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/synthesizer.c | 56 ++++++++++++++++++++++-- dlls/windows.media.speech/tests/speech.c | 22 +++++----- 2 files changed, 62 insertions(+), 16 deletions(-)
diff --git a/dlls/windows.media.speech/synthesizer.c b/dlls/windows.media.speech/synthesizer.c index a94447a9c49..0a607dda543 100644 --- a/dlls/windows.media.speech/synthesizer.c +++ b/dlls/windows.media.speech/synthesizer.c @@ -848,6 +848,7 @@ struct synthesizer LONG ref;
struct synthesizer_options *options; + IVoiceInformation *current_voice; };
/* @@ -911,6 +912,8 @@ static ULONG WINAPI synthesizer_Release( ISpeechSynthesizer *iface ) { if (impl->options) ISpeechSynthesizerOptions_Release(&impl->options->ISpeechSynthesizerOptions_iface); + if (impl->current_voice) + IVoiceInformation_Release(impl->current_voice); free(impl); }
@@ -963,14 +966,49 @@ static HRESULT WINAPI synthesizer_SynthesizeSsmlToStreamAsync( ISpeechSynthesize
static HRESULT WINAPI synthesizer_put_Voice( ISpeechSynthesizer *iface, IVoiceInformation *value ) { - FIXME("iface %p, value %p stub.\n", iface, value); - return E_NOTIMPL; + struct synthesizer *impl = impl_from_ISpeechSynthesizer(iface); + IVoiceInformation *voice; + HSTRING id, id2; + HRESULT hr; + INT32 cmp, idx; + + TRACE("iface %p, value %p semi-stub.\n", iface, value); + + hr = IVoiceInformation_get_Id(value, &id); + if (FAILED(hr)) return hr; + + for (idx = 0; ; idx++) + { + if (SUCCEEDED(hr = IVectorView_VoiceInformation_GetAt(&all_voices.IVectorView_VoiceInformation_iface, idx, &voice))) + { + if (SUCCEEDED(hr = IVoiceInformation_get_Id(voice, &id2))) + { + hr = WindowsCompareStringOrdinal(id, id2, &cmp); + WindowsDeleteString(id2); + } + IVoiceInformation_Release(voice); + } + if (FAILED(hr) || cmp == 0) break; + } + WindowsDeleteString(id); + + if (SUCCEEDED(hr)) + { + if (impl->current_voice) + IVoiceInformation_Release(impl->current_voice); + IVoiceInformation_AddRef(impl->current_voice = value); + } + return hr; }
static HRESULT WINAPI synthesizer_get_Voice( ISpeechSynthesizer *iface, IVoiceInformation **value ) { - FIXME("iface %p, value %p stub.\n", iface, value); - return E_NOTIMPL; + struct synthesizer *impl = impl_from_ISpeechSynthesizer(iface); + + TRACE("iface %p, value %p.\n", iface, value); + if (!impl->current_voice) return E_NOTIMPL; + IVoiceInformation_AddRef(*value = impl->current_voice); + return S_OK; }
static const struct ISpeechSynthesizerVtbl synthesizer_vtbl = @@ -1135,6 +1173,7 @@ static HRESULT WINAPI factory_GetTrustLevel( IActivationFactory *iface, TrustLev
static HRESULT WINAPI factory_ActivateInstance( IActivationFactory *iface, IInspectable **instance ) { + struct IVoiceInformation *static_voice; struct synthesizer *impl; HRESULT hr;
@@ -1155,6 +1194,15 @@ static HRESULT WINAPI factory_ActivateInstance( IActivationFactory *iface, IInsp impl->ISpeechSynthesizer_iface.lpVtbl = &synthesizer_vtbl; impl->ISpeechSynthesizer2_iface.lpVtbl = &synthesizer2_vtbl; impl->IClosable_iface.lpVtbl = &closable_vtbl; + /* assuming default is the first one... */ + hr = IVectorView_VoiceInformation_GetAt(&all_voices.IVectorView_VoiceInformation_iface, 0, &static_voice); + if (SUCCEEDED(hr)) + hr = voice_information_clone(static_voice, &impl->current_voice); + if (FAILED(hr)) + { + free(impl); + return hr; + } impl->ref = 1;
*instance = (IInspectable *)&impl->ISpeechSynthesizer_iface; diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index 9c5517adda8..6450fc29611 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -870,6 +870,7 @@ static void test_SpeechSynthesizer(void) UINT32 size, idx; BOOLEAN found; ULONG ref; + INT32 cmp;
hr = RoInitialize(RO_INIT_MULTITHREADED); ok(hr == S_OK, "RoInitialize failed, hr %#lx\n", hr); @@ -1018,21 +1019,18 @@ static void test_SpeechSynthesizer(void) ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
hr = ISpeechSynthesizer_get_Voice(synthesizer, &voice); - todo_wine ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
- if (hr == S_OK) - { - INT32 cmp; - IVoiceInformation_get_Id(voice, &str); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - hr = WindowsCompareStringOrdinal(str, default_voice_id, &cmp); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Id(voice, &str); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
- hr = WindowsDeleteString(str); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = WindowsCompareStringOrdinal(str, default_voice_id, &cmp); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
- IVoiceInformation_Release(voice); - } + hr = WindowsDeleteString(str); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + IVoiceInformation_Release(voice);
hr = WindowsDeleteString(default_voice_id); ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
V2 pushed, including Bernhard's comments. V3 pushed, should make pipeline happier.
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/synthesizer.c:
TRACE("iface %p, ref %lu.\n", iface, ref); if (!ref)
- {
if (impl->options)
ISpeechSynthesizerOptions_Release(&impl->options->ISpeechSynthesizerOptions_iface);
The if here seems unnecessary.
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/synthesizer.c:
+static HRESULT synthesizer_options_create( struct synthesizer_options **out ) +{
- struct synthesizer_options *options;
- if (!(options = calloc(1, sizeof(*options)))) return E_OUTOFMEMORY;
- options->ISpeechSynthesizerOptions_iface.lpVtbl = &synthesizer_options_vtbl;
- options->ISpeechSynthesizerOptions2_iface.lpVtbl = &synthesizer_options2_vtbl;
- options->ISpeechSynthesizerOptions3_iface.lpVtbl = &synthesizer_options3_vtbl;
- /* all other values default to 0 or false */
- options->audio_pitch = 1.0;
- options->audio_volume = 1.0;
- options->speaking_rate = 1.0;
- *out = options;
I'd prefer it if you return the IIface pointer here instead of the struct, as these create functions are like class constructors to me. If you need the struct later on just use impl_from_I...
Also this makes the "Created iface ..." trace clearer imo.
On Wed Jun 21 09:06:30 2023 +0000, Bernhard Kölbl wrote:
I'd prefer it if you return the IIface pointer here instead of the struct, as these create functions are like class constructors to me. If you need the struct later on just use impl_from_I... Also this makes the "Created iface ..." trace clearer imo.
Oh and make sure to set ref to 1 here.
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/synthesizer.c:
- voice_information_GetIids,
- voice_information_GetRuntimeClassName,
- voice_information_GetTrustLevel,
- /*** IVoiceInformation methods ***/
- voice_information_get_DisplayName,
- voice_information_get_Id,
- voice_information_get_Language,
- voice_information_get_Description,
- voice_information_get_Gender,
+};
+HRESULT voice_information_create(const WCHAR *display_name, const WCHAR *id, const WCHAR *locale,
VoiceGender gender, IVoiceInformation **pvoice)
+{
- struct voice_information *voice_info;
Please avoid Hungarian Notation like names `pvoice` -> `voice_information`, also keep in mind line limit for Speech is 160 (instead of 130).
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/synthesizer.c:
factory_ActivateInstance,
};
+static HRESULT dummy_provider_init(struct synth_voices_provider *provider) +{
- HRESULT hr;
- WCHAR locale[LOCALE_NAME_MAX_LENGTH];
- if (GetUserDefaultLocaleName(locale, ARRAY_SIZE(locale)) > ARRAY_SIZE(locale))
return E_OUTOFMEMORY;
This seems to be `E_NOT_SUFFICIENT_BUFFER`
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/synthesizer.c:
factory_ActivateInstance,
};
+static HRESULT dummy_provider_init(struct synth_voices_provider *provider) +{
- HRESULT hr;
- WCHAR locale[LOCALE_NAME_MAX_LENGTH];
- if (GetUserDefaultLocaleName(locale, ARRAY_SIZE(locale)) > ARRAY_SIZE(locale))
return E_OUTOFMEMORY;
- provider->voices = calloc(1, sizeof(all_voices.provider.voices[0]));
- if (!provider->voices) return E_OUTOFMEMORY;
- hr = voice_information_create(L"Dummy voice", L"--noid--", locale, VoiceGender_Male, &provider->voices[0]);
- if (FAILED(hr))
- {
The preceding lines can be squashed into 2
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/synthesizer.c:
DEFINE_IINSPECTABLE(installed_voices_static, IInstalledVoicesStatic, struct synthesizer_statics, IActivationFactory_iface)
+static CRITICAL_SECTION allvoices_cs; +static CRITICAL_SECTION_DEBUG allvoices_critsect_debug = +{
- 0, 0, &allvoices_cs,
- { &allvoices_critsect_debug.ProcessLocksList, &allvoices_critsect_debug.ProcessLocksList },
0, 0, { (DWORD_PTR)(__FILE__ ": allvoices_cs") }
+}; +static CRITICAL_SECTION allvoices_cs = { &allvoices_critsect_debug, -1, 0, 0, 0, 0 };
About this cs, would something like this work out?
```c static struct synthesizer_statics synthesizer_statics = { .IActivationFactory_iface = {&factory_vtbl}, .IInstalledVoicesStatic_iface = {&installed_voices_static_vtbl}, .ref = 1,
.allvoices_critsect_debug = { 0, 0, &synthesizer_statics.allvoices_cs, { &synthesizer_statics.allvoices_critsect_debug.ProcessLocksList, &synthesizer_statics.allvoices_critsect_debug.ProcessLocksList }, 0, 0, { (DWORD_PTR)(__FILE__ ": synthesizer_statics_cs") } }, .allvoices_cs = { &synthesizer_statics.allvoices_critsect_debug, -1, 0, 0, 0, 0 } }; ```
then just
```c static HRESULT WINAPI installed_voices_static_get_AllVoices( IInstalledVoicesStatic *iface, IVectorView_VoiceInformation **value ) { struct synthesizer_statics *impl = impl_from_IInstalledVoicesStatic(iface); HRESULT hr;
TRACE("iface %p, value %p.\n", iface, value);
EnterCriticalSection(&impl->allvoices_cs); if (all_voices.provider.num_voices == 0) ... ```
On Wed Jun 21 12:48:58 2023 +0000, Bernhard Kölbl wrote:
About this cs, would something like this work out?
static struct synthesizer_statics synthesizer_statics = { .IActivationFactory_iface = {&factory_vtbl}, .IInstalledVoicesStatic_iface = {&installed_voices_static_vtbl}, .ref = 1, .allvoices_critsect_debug = { 0, 0, &synthesizer_statics.allvoices_cs, { &synthesizer_statics.allvoices_critsect_debug.ProcessLocksList, &synthesizer_statics.allvoices_critsect_debug.ProcessLocksList }, 0, 0, { (DWORD_PTR)(__FILE__ ": synthesizer_statics_cs") } }, .allvoices_cs = { &synthesizer_statics.allvoices_critsect_debug, -1, 0, 0, 0, 0 } };
then just
static HRESULT WINAPI installed_voices_static_get_AllVoices( IInstalledVoicesStatic *iface, IVectorView_VoiceInformation **value ) { struct synthesizer_statics *impl = impl_from_IInstalledVoicesStatic(iface); HRESULT hr; TRACE("iface %p, value %p.\n", iface, value); EnterCriticalSection(&impl->allvoices_cs); if (all_voices.provider.num_voices == 0) ...
You probably could then also just rename `allvoices_cs` -> `cs` and `allvoices_critsect_debug` -> `cs_debug`
Generally, I'd prefer focusing on the first 4 commits for now, maybe open a new MR for them, so we can get them in. :smile: