SpeechSynthesizer 无法获取所有已安装的语音 3
SpeechSynthesizer doesn't get all installed voices 3
我在区域和语言下使用 "Add language" 添加了很多语音。这些出现在语音中的文本到语音下。 (我用的是Windows10)
我想在 System.Speech.Synthesis
.
中的 SpeechSynthesizer
class 应用中使用这些
在我的应用程序中列出可用语音时,仅显示了少数实际可用的语音:
static void Main()
{
SpeechSynthesizer speech = new SpeechSynthesizer();
ReadOnlyCollection<InstalledVoice> voices = speech.GetInstalledVoices();
if (File.Exists("available_voices.txt"))
{
File.WriteAllText("available_voices.txt", string.Empty);
}
using (StreamWriter sw = File.AppendText("available_voices.txt"))
{
foreach (InstalledVoice voice in voices)
{
sw.WriteLine(voice.VoiceInfo.Name);
}
}
}
在available_voices.txt
中查找只列出了这些声音:
Microsoft David Desktop
Microsoft Hazel Desktop
Microsoft Zira Desktop
Microsoft Irina Desktop
但是在设置中的文本到语音下查看还有更多,例如 Microsoft George
和 Microsoft Mark
。
此处接受的答案:
SpeechSynthesizer doesn't get all installed voices
建议将平台更改为 x86。我试过了,但没有看到任何变化。
这个答案:
SpeechSynthesizer doesn't get all installed voices 2
由于 System.Speech.Synthesis
中的错误,建议使用 .NET v4.5。我的目标是 .NET Framework 4.5,但我仍然只能检索 4 个声音。
None 我链接的问题的答案帮助我解决了我的问题,所以我再次提问。感谢任何帮助。
我通过从其他来源安装语音并获取 Microsoft Speech Platform - Runtime(版本 11)解决了这个问题
可以在 Microsoft 上找到可用的语音 website(单击红色下载按钮,应会列出语音)
在尝试了所有已发布的解决方案后,我通过编辑注册表解决了这个问题:
正在复制 Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Speech_OneCore\Voices\Tokens\MSTTS_V110_heIL_Asaf
(其中MSTTS_V110_heIL_Asaf
是我想在.NET中使用的语音的注册表文件夹,但没有出现在GetInstalledVoices()
中)
到看起来相同但不是 Speech_OneCore
的注册表地址只是 Speech
.
从技术上讲,为了复制注册表文件夹,我导出了原始文件夹,然后编辑 .reg 文件以将 Speech OneCore
更改为 Speech
,然后应用新的 .reg 文件。
Microsoft 网站上的 Microsoft Speech Platform - Runtime Languages(版本 11)似乎只包含已安装的语言。不是可以在 Speech_OneCore.
下找到的那些
提出原始问题 3 年后,API 似乎包含相同的问题,因此这里有一个更“深入”的答案。
TL;DR;代码示例-底部
语音列表的问题是 Microsoft Speech API 的奇怪设计 - Windows 中有两组语音在注册表的不同位置注册 - 一组在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices,另一个 - 在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCore\Voices.
问题是 SpeechSynthesizer 的(或更具体地说 - VoiceSynthesis's) initialization routine is nailed 到第一个,同时我们通常需要两者的组合。
所以,实际上有两种方法可以克服这种行为。
选项 1(其他答案中提到的那个):操纵注册表以物理复制来自[=61的语音定义记录=] 注册表,使它们对 SpeechSynthesizer 可见。这里有很多选择:手动注册表操作、PowerShell 脚本、code-based 等
选项 2(我在我的项目中使用的那个):使用反射将额外的声音放入内部 VoiceSyntesis 的_installedVoices 字段,有效地模拟了微软在他们的代码中所做的事情。
好消息是 Speech API 源代码现已开放,因此我们不必在黑暗中摸索,试图理解我们需要做什么。
这是 original code 片段:
using (ObjectTokenCategory category = ObjectTokenCategory.Create(SAPICategories.Voices))
{
if (category != null)
{
// Build a list with all the voicesInfo
foreach (ObjectToken voiceToken in category.FindMatchingTokens(null, null))
{
if (voiceToken != null && voiceToken.Attributes != null)
{
voices.Add(new InstalledVoice(voiceSynthesizer, new VoiceInfo(voiceToken)));
}
}
}
}
我们只需要用另一个注册表项路径替换 SAPICategories.Voices 常量并重复整个配方。
坏消息是这里使用的所有需要的类、方法和字段都是内部的,因此我们将不得不大量使用反射来实例化类、调用方法和get/set字段。
请在下面找到我的实施示例 - 您在合成器上调用 InjectOneCoreVoices 扩展方法,它完成了工作。请注意,如果出现问题,它会抛出异常,所以不要忘记适当的 try/catch 环境。
public static class SpeechApiReflectionHelper
{
private const string PROP_VOICE_SYNTHESIZER = "VoiceSynthesizer";
private const string FIELD_INSTALLED_VOICES = "_installedVoices";
private const string ONE_CORE_VOICES_REGISTRY = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCore\Voices";
private static readonly Type ObjectTokenCategoryType = typeof(SpeechSynthesizer).Assembly
.GetType("System.Speech.Internal.ObjectTokens.ObjectTokenCategory")!;
private static readonly Type VoiceInfoType = typeof(SpeechSynthesizer).Assembly
.GetType("System.Speech.Synthesis.VoiceInfo")!;
private static readonly Type InstalledVoiceType = typeof(SpeechSynthesizer).Assembly
.GetType("System.Speech.Synthesis.InstalledVoice")!;
public static void InjectOneCoreVoices(this SpeechSynthesizer synthesizer)
{
var voiceSynthesizer = GetProperty(synthesizer, PROP_VOICE_SYNTHESIZER);
if (voiceSynthesizer == null) throw new NotSupportedException($"Property not found: {PROP_VOICE_SYNTHESIZER}");
var installedVoices = GetField(voiceSynthesizer, FIELD_INSTALLED_VOICES) as IList;
if (installedVoices == null)
throw new NotSupportedException($"Field not found or null: {FIELD_INSTALLED_VOICES}");
if (ObjectTokenCategoryType
.GetMethod("Create", BindingFlags.Static | BindingFlags.NonPublic)?
.Invoke(null, new object?[] {ONE_CORE_VOICES_REGISTRY}) is not IDisposable otc)
throw new NotSupportedException($"Failed to call Create on {ObjectTokenCategoryType} instance");
using (otc)
{
if (ObjectTokenCategoryType
.GetMethod("FindMatchingTokens", BindingFlags.Instance | BindingFlags.NonPublic)?
.Invoke(otc, new object?[] {null, null}) is not IList tokens)
throw new NotSupportedException($"Failed to list matching tokens");
foreach (var token in tokens)
{
if (token == null || GetProperty(token, "Attributes") == null) continue;
var voiceInfo =
typeof(SpeechSynthesizer).Assembly
.CreateInstance(VoiceInfoType.FullName!, true,
BindingFlags.Instance | BindingFlags.NonPublic, null,
new object[] {token}, null, null);
if (voiceInfo == null)
throw new NotSupportedException($"Failed to instantiate {VoiceInfoType}");
var installedVoice =
typeof(SpeechSynthesizer).Assembly
.CreateInstance(InstalledVoiceType.FullName!, true,
BindingFlags.Instance | BindingFlags.NonPublic, null,
new object[] {voiceSynthesizer, voiceInfo}, null, null);
if (installedVoice == null)
throw new NotSupportedException($"Failed to instantiate {InstalledVoiceType}");
installedVoices.Add(installedVoice);
}
}
}
private static object? GetProperty(object target, string propName)
{
return target.GetType().GetProperty(propName, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(target);
}
private static object? GetField(object target, string propName)
{
return target.GetType().GetField(propName, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(target);
}
}
抱歉,如果我的回答在主题发布后来得太晚,但我开发了一个小工具,可以修补已安装的声音,使它们可用于 .NET text-to-speech 引擎。
该工具将所选项目从“HKLM\SOFTWARE\Microsoft\Speech_OneCore\Voices\Tokens”键复制到“HKLM\SOFTWARE\Microsoft\Speech\Voices\Tokens”。
如果您有兴趣:TTSVoicePatcher(它是免费软件,FR/EN)
由于 HKLM 中的键操作,该工具需要管理员权限才能启动。
我在区域和语言下使用 "Add language" 添加了很多语音。这些出现在语音中的文本到语音下。 (我用的是Windows10)
我想在 System.Speech.Synthesis
.
SpeechSynthesizer
class 应用中使用这些
在我的应用程序中列出可用语音时,仅显示了少数实际可用的语音:
static void Main()
{
SpeechSynthesizer speech = new SpeechSynthesizer();
ReadOnlyCollection<InstalledVoice> voices = speech.GetInstalledVoices();
if (File.Exists("available_voices.txt"))
{
File.WriteAllText("available_voices.txt", string.Empty);
}
using (StreamWriter sw = File.AppendText("available_voices.txt"))
{
foreach (InstalledVoice voice in voices)
{
sw.WriteLine(voice.VoiceInfo.Name);
}
}
}
在available_voices.txt
中查找只列出了这些声音:
Microsoft David Desktop
Microsoft Hazel Desktop
Microsoft Zira Desktop
Microsoft Irina Desktop
但是在设置中的文本到语音下查看还有更多,例如 Microsoft George
和 Microsoft Mark
。
此处接受的答案: SpeechSynthesizer doesn't get all installed voices 建议将平台更改为 x86。我试过了,但没有看到任何变化。
这个答案:
SpeechSynthesizer doesn't get all installed voices 2
由于 System.Speech.Synthesis
中的错误,建议使用 .NET v4.5。我的目标是 .NET Framework 4.5,但我仍然只能检索 4 个声音。
None 我链接的问题的答案帮助我解决了我的问题,所以我再次提问。感谢任何帮助。
我通过从其他来源安装语音并获取 Microsoft Speech Platform - Runtime(版本 11)解决了这个问题
可以在 Microsoft 上找到可用的语音 website(单击红色下载按钮,应会列出语音)
在尝试了所有已发布的解决方案后,我通过编辑注册表解决了这个问题:
正在复制 Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Speech_OneCore\Voices\Tokens\MSTTS_V110_heIL_Asaf
(其中MSTTS_V110_heIL_Asaf
是我想在.NET中使用的语音的注册表文件夹,但没有出现在GetInstalledVoices()
中)
到看起来相同但不是 Speech_OneCore
的注册表地址只是 Speech
.
从技术上讲,为了复制注册表文件夹,我导出了原始文件夹,然后编辑 .reg 文件以将 Speech OneCore
更改为 Speech
,然后应用新的 .reg 文件。
Microsoft 网站上的 Microsoft Speech Platform - Runtime Languages(版本 11)似乎只包含已安装的语言。不是可以在 Speech_OneCore.
下找到的那些提出原始问题 3 年后,API 似乎包含相同的问题,因此这里有一个更“深入”的答案。
TL;DR;代码示例-底部
语音列表的问题是 Microsoft Speech API 的奇怪设计 - Windows 中有两组语音在注册表的不同位置注册 - 一组在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices,另一个 - 在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCore\Voices.
问题是 SpeechSynthesizer 的(或更具体地说 - VoiceSynthesis's) initialization routine is nailed 到第一个,同时我们通常需要两者的组合。
所以,实际上有两种方法可以克服这种行为。
选项 1(其他答案中提到的那个):操纵注册表以物理复制来自[=61的语音定义记录=] 注册表,使它们对 SpeechSynthesizer 可见。这里有很多选择:手动注册表操作、PowerShell 脚本、code-based 等
选项 2(我在我的项目中使用的那个):使用反射将额外的声音放入内部 VoiceSyntesis 的_installedVoices 字段,有效地模拟了微软在他们的代码中所做的事情。
好消息是 Speech API 源代码现已开放,因此我们不必在黑暗中摸索,试图理解我们需要做什么。
这是 original code 片段:
using (ObjectTokenCategory category = ObjectTokenCategory.Create(SAPICategories.Voices))
{
if (category != null)
{
// Build a list with all the voicesInfo
foreach (ObjectToken voiceToken in category.FindMatchingTokens(null, null))
{
if (voiceToken != null && voiceToken.Attributes != null)
{
voices.Add(new InstalledVoice(voiceSynthesizer, new VoiceInfo(voiceToken)));
}
}
}
}
我们只需要用另一个注册表项路径替换 SAPICategories.Voices 常量并重复整个配方。
坏消息是这里使用的所有需要的类、方法和字段都是内部的,因此我们将不得不大量使用反射来实例化类、调用方法和get/set字段。
请在下面找到我的实施示例 - 您在合成器上调用 InjectOneCoreVoices 扩展方法,它完成了工作。请注意,如果出现问题,它会抛出异常,所以不要忘记适当的 try/catch 环境。
public static class SpeechApiReflectionHelper
{
private const string PROP_VOICE_SYNTHESIZER = "VoiceSynthesizer";
private const string FIELD_INSTALLED_VOICES = "_installedVoices";
private const string ONE_CORE_VOICES_REGISTRY = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCore\Voices";
private static readonly Type ObjectTokenCategoryType = typeof(SpeechSynthesizer).Assembly
.GetType("System.Speech.Internal.ObjectTokens.ObjectTokenCategory")!;
private static readonly Type VoiceInfoType = typeof(SpeechSynthesizer).Assembly
.GetType("System.Speech.Synthesis.VoiceInfo")!;
private static readonly Type InstalledVoiceType = typeof(SpeechSynthesizer).Assembly
.GetType("System.Speech.Synthesis.InstalledVoice")!;
public static void InjectOneCoreVoices(this SpeechSynthesizer synthesizer)
{
var voiceSynthesizer = GetProperty(synthesizer, PROP_VOICE_SYNTHESIZER);
if (voiceSynthesizer == null) throw new NotSupportedException($"Property not found: {PROP_VOICE_SYNTHESIZER}");
var installedVoices = GetField(voiceSynthesizer, FIELD_INSTALLED_VOICES) as IList;
if (installedVoices == null)
throw new NotSupportedException($"Field not found or null: {FIELD_INSTALLED_VOICES}");
if (ObjectTokenCategoryType
.GetMethod("Create", BindingFlags.Static | BindingFlags.NonPublic)?
.Invoke(null, new object?[] {ONE_CORE_VOICES_REGISTRY}) is not IDisposable otc)
throw new NotSupportedException($"Failed to call Create on {ObjectTokenCategoryType} instance");
using (otc)
{
if (ObjectTokenCategoryType
.GetMethod("FindMatchingTokens", BindingFlags.Instance | BindingFlags.NonPublic)?
.Invoke(otc, new object?[] {null, null}) is not IList tokens)
throw new NotSupportedException($"Failed to list matching tokens");
foreach (var token in tokens)
{
if (token == null || GetProperty(token, "Attributes") == null) continue;
var voiceInfo =
typeof(SpeechSynthesizer).Assembly
.CreateInstance(VoiceInfoType.FullName!, true,
BindingFlags.Instance | BindingFlags.NonPublic, null,
new object[] {token}, null, null);
if (voiceInfo == null)
throw new NotSupportedException($"Failed to instantiate {VoiceInfoType}");
var installedVoice =
typeof(SpeechSynthesizer).Assembly
.CreateInstance(InstalledVoiceType.FullName!, true,
BindingFlags.Instance | BindingFlags.NonPublic, null,
new object[] {voiceSynthesizer, voiceInfo}, null, null);
if (installedVoice == null)
throw new NotSupportedException($"Failed to instantiate {InstalledVoiceType}");
installedVoices.Add(installedVoice);
}
}
}
private static object? GetProperty(object target, string propName)
{
return target.GetType().GetProperty(propName, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(target);
}
private static object? GetField(object target, string propName)
{
return target.GetType().GetField(propName, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(target);
}
}
抱歉,如果我的回答在主题发布后来得太晚,但我开发了一个小工具,可以修补已安装的声音,使它们可用于 .NET text-to-speech 引擎。
该工具将所选项目从“HKLM\SOFTWARE\Microsoft\Speech_OneCore\Voices\Tokens”键复制到“HKLM\SOFTWARE\Microsoft\Speech\Voices\Tokens”。
如果您有兴趣:TTSVoicePatcher(它是免费软件,FR/EN)
由于 HKLM 中的键操作,该工具需要管理员权限才能启动。