【问题标题】:SpeechSynthesizer doesn't get all installed voices 3SpeechSynthesizer 无法获取所有已安装的声音 3
【发布时间】:2019-01-19 13:38:23
【问题描述】:

我使用区域和语言下的“添加语言”添加了许多声音。这些出现在语音中的文本到语音下。 (我使用的是 Windows 10)

我想在我的应用程序中将这些与System.Speech.Synthesis 中的SpeechSynthesizer 类一起使用。

在我的应用程序中列出可用语音时,仅显示少数实际可用的语音:

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 GeorgeMicrosoft Mark

这里接受的答案: SpeechSynthesizer doesn't get all installed voices 建议将平台更改为x86。我试过这个,但我没有看到任何变化。

这个答案: SpeechSynthesizer doesn't get all installed voices 2 建议使用 .NET v4.5,因为 System.Speech.Synthesis 中存在错误。我的目标是 .NET Framework 4.5,但我仍然只能检索 4 个声音。

我链接的问题中的答案都没有帮助我解决我的问题,所以我再次询问。任何帮助都将不胜感激。

【问题讨论】:

    标签: c# .net visual-studio-2017 windows-10 text-to-speech


    【解决方案1】:

    在尝试了所有已发布的解决方案后,我通过编辑注册表解决了它:
    复制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 文件。

    【讨论】:

      【解决方案2】:

      我通过从其他来源安装语音并获取 Microsoft Speech Platform - Runtime(第 11 版)解决了这个问题

      可以在microsoftwebsite上找到可用的声音(点击红色的下载按钮,声音应该会列出来)

      【讨论】:

      • 嗯,此链接位于 Microsoft Speech Platform - Runtime Languages (Version 11) 上。我看不到要在那里安装的语言。那个东西已经安装在我的电脑上。
      • @Kosmos 抱歉,回答晚了,但如果您按下载,则会显示可用语言的列表。 (编辑答案)
      【解决方案3】:

      在提出原始问题 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 的)初始化例程对于第一个例程是 nailed,同时我们通常需要两者的组合。

      所以,实际上有两种方法可以克服这种行为。

      选项 1(在其他答案中提到的那个):操纵注册表以物理复制 Speech_OneCore 注册表中的语音定义记录,使其对 SpeechSynthesizer 可见。在这里您有很多选择:手动注册表操作、PowerShell 脚本、基于代码等。

      选项 2(我在项目中使用的那个):使用反射将额外的声音放入内部 VoiceSyntesis 的 _installedVoices 字段,有效模拟微软在他们的代码。

      好消息是 Speech API 源代码现已开放,因此我们不必在黑暗中摸索以了解我们需要做什么。

      这是original code sn-p:

      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 常量替换为另一个注册表项路径并重复整个配方。

      坏消息是这里使用的所有需要​​的类、方法和字段都是内部的,因此我们必须广泛使用反射来实例化类、调用方法和获取/设置字段。

      请在下面找到我的实现示例 - 您在合成器上调用 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);
          }
      }
      

      【讨论】:

        【解决方案4】:

        Microsoft 网站上的 Microsoft Speech Platform - Runtime Languages(版本 11)似乎只包含已安装的语言。不是可以在 Speech_OneCore 下找到的。

        【讨论】:

          猜你喜欢
          • 2011-03-13
          • 2012-08-27
          • 2015-11-02
          • 2016-02-15
          • 1970-01-01
          • 2019-07-10
          • 2016-03-13
          • 1970-01-01
          • 2023-03-11
          相关资源
          最近更新 更多