让我们看一下关于如何从托管应用程序使用语音合成的示例。作为最典型的一个 UI 输出示例,我将从只说出“Hello, world”的应用程序开始,如图 5 所示。
using System; using System.Speech.Synthesis; namespace TTS_Console_Sample_1 { class Program { static void Main(string[] args) { SpeechSynthesizer synth = new SpeechSynthesizer(); synth.SpeakText("Hello, world!"); } } }
该示例是一个明显的控制台应用程序,是最近使用 Visual C#® 创建的,其中添加了三行代码。所添加的第一行只引入 System.Speech.Synthesis 命名空间。第二行声明并实例化 SpeechSynthesizer 的实例,它准确表示了其名称的含意:语音合成器。所添加的第三行是对 SpeakText 的调用。这是调用合成器所需的全部。
默认情况下,SpeechSynthesizer 类使用 Speech 控件面板中默认推荐的合成器。但是它可以使用任何与 SAPI DDI 兼容的合成器。
下一个示例(请参见图 6)显示如何进行此操作,该操作使用 Windows 2000 和 Windows XP 的旧式 Sam 语音,以及新 Anna 和 Windows Vista 的 Microsoft® Lili 语音。(请注意,该示例以及其他所有 System.Speech.Synthesis 示例都使用和第一个示例相同的代码框架,并替换 Main 的主体。)该示例显示了使用所需合成器名称的 SelectVoice 方法的三个实例。它还演示了 Windows Vista Chinese 合成器(即 Lili)的用法。另外,Lili 也可以很好地说英语。
SpeechSynthesizer synth = new SpeechSynthesizer(); synth.SelectVoice("Microsoft Sam"); synth.SpeakText("I'm Sam."); synth.SpeakText("You may have heard me speaking to you in Windows XP."); synth.SpeakText("Anna will make me redundant."); synth.SelectVoice("Microsoft Anna"); synth.SpeakText("I am the new voice in Windows."); synth.SpeakText("Sam belongs to a previous generation."); synth.SpeakText("I sound great."); synth.SelectVoice("Microsoft Lili"); synth.SpeakText("我是在北京被研究开发的 我使用了专业播音员的声音。每 个听到过我说话的人都说我是中文语音合成中最棒的!"); // Requires MS Mincho and SimSun fonts to view /* "I was developed in Beijing, using recordings of a professional news reader. Everybody who hears me talk says that I am the best synthesized Chinese voice they have ever heard!" */
在这两个示例中,我使用合成 API 的方式非常类似于我使用控制台 API 的方式:应用程序只发送字符,然后这些字符立即串联呈现。但是对于更复杂的输出,更容易将合成看作文档呈现的等价物,其中合成器的输入是一个文档,该文档不仅包含要呈现的内容,还包括要在该内容的特定点应用的不同效果和设置。
SpeechSynthesizer 类可以使用名为语音合成标记语言(Speech Synthesis Markup Language,SSML)的 XML 文档格式,这非常类似于描述要应用到 Web 页特定内容片段中的呈现风格和结构的 XHTML 文档。W3C SSML 推荐 (www.w3.org/TR/speech-synthesis) 非常具有可读性,因此在本文中,我不打算深入描述SSML。可以肯定地说,应用程序可以简单地将 SSML 文档直接加载到合成器并使之呈现出来。以下是一个加载并呈现 SSML 文件的示例:
SpeechSynthesizer synth = new SpeechSynthesizer(); PromptBuilder savedPrompt = new PromptBuilder(); savedPrompt.AppendSsml("c:\\prompt.ssml"); synth.Speak(SavedPrompt);
编写 SSML 文件的另一个简便方法是在 System.Speech.Synthesis 中使用 PromptBuilder 类。PromptBuilder 几乎可以表示 SSML 文档可以表示的任何内容,而且更容易使用(请参见图 7)。用于创建复杂合成的通用模型首先使用 PromptBuilder 以您希望的方式生成提示符,然后使用合成器的 Speak 或 SpeakAsync 方法呈现它。
//This prompt is quite complicated //So I'm going to build it first, and then render it. PromptBuilder myPrompt = new PromptBuilder(); //Start the main speaking style PromptStyle mainStyle = new PromptStyle(); mainStyle.Rate = PromptRate.Medium; mainStyle.Volume = PromptVolume.Loud; myPrompt.StartStyle(MainStyle); //Alert the listener myPrompt.AppendAudio(new Uri( "file://c:\\windows\\media\\notify.wav"), "Attention!"); myPrompt.AppendText("Here are some important messages."); //Here's the first important message myPrompt.AppendTextWithPronunciation("WinFX", "wɪnɛfɛks"); myPrompt.AppendText("is a great platform."); //And the second one myPrompt.AppendTextWithHint("ASP", SayAs.Acronym); myPrompt.AppendText( "is an acronym for Active Server Pages. Whereas an ASP is a snake."); myPrompt.AppendBreak(); //Let's emphasise how important these messages are PromptStyle interimStyle = new PromptStyle(); interimStyle.Emphasis = PromptEmphasis.Strong; myPrompt.StartStyle(interimStyle); myPrompt.AppendText("Please remember these two things."); myPrompt.EndStyle(); //Then we can revert to the main speaking style myPrompt.AppendBreak(); myPrompt.AppendText("Thank you"); myPrompt.EndStyle(); //Now let's get the synthesizer to render this message SpeechSynthesizer synth = new SpeechSynthesizer(); synth.Speak(myPrompt);
图 7 阐释 PromptBuilder 的大量强大功能。需要指出的首要一点是,它生成一个带有分层结构的文档。该示例使用的说话风格嵌套在另一个中。在该文档的开头,我使用了将在整个文档中使用的风格。然后在文档进行到一半的时候,我使用了另一种风格以示重点。当我结束这种风格时,文档又自动转换为以前的风格。
该示例也显示了其他很多方便的功能。AppendAudio 功能使 WAV 文件与输出结合,如果未找到 WAV 文件,可以使用一个等效文本文件。AppendTextWithPronunciation 功能允许您指定单词的正确发音。通过联合使用用于推导出未知单词发音的词典和算法,语音合成引擎已经知道如何对一种语言中的多数常用单词进行发音。但这并不对所有单词都奏效,例如,某些专用术语或商标名称。例如,“WinFX”将可能发音为“winfeks”。相反,我使用 International Phonetic Alphabet 将“WinFX”描述为“w?n?f?ks”,其中字母“?”是 Unicode 字符 0x026A(“i”的发音同“fish”中的“i”,不同于“five”中的“i”),而字母“?”是 Unicode character 0x025B(通用美语中“e”的发音同“bed”中的“e”)。
通常,合成引擎可以区分缩写和大写单词。但偶尔有些时候,您会发现一个缩写词由引擎的试探法错误地演绎为一个单词。因此,您可以使用 AppendTextWithHint 功能为缩写词作标记。对于 PromptBuilder 而言有很多细微差别。我的示例虽然不太全面,但非常具有说明性。
将内容规范从运行时呈现中分离出来的另一个好处是,之后您可以随意将应用程序从它呈现的特定内容分离出来。您可以利用 PromptBuilder 使其提示符作为 SSML 由该应用程序的另一部分或一个完全不同的应用程序加载。以下代码使用 PromptBuilder 写入一个 SSML 文件:
using(StreamWriter promptWriter = new StreamWriter("c:\\prompt.ssml")) { promptWriter.Write(myPrompt.ToXml()); }
分离内容片段的另一个方法是将整个提示符呈现给一个音频文件以用于稍后重播:
SpeechSynthesizer synth = new SpeechSynthesizer(); synth.SetOutputToWaveFile("c:\\message.wav"); synth.Speak(myPrompt); synth.SetOutputToNull();
使用 SSML 标记还是使用 PromptBuilder 类,可能取决于您偏好的风格。您应该使用自己觉得更舒服的那个。
对于 SSML 和 PromptBuilder,需要注意的最后一点是,每个合成器的功能将稍有不同。因此,对于使用这两种机制中的任一种请求的特定行为,如果该引擎有能力采取该行为,则应该将这些行为认为是该引擎将采用的建议性请求。