与上一篇相比,这篇是用的C#.net开发,讲述的更加清晰

1.   TTS概述

 

随着语音技术的发展,微软也推出了相应的语音开发工具,即Microsoft Speech SDK,这个SDK中包含了语音应用设计接口(SAPI)、微软的连续语音识别引擎(MCSR)以及微软的语音合成(TTS)引擎等等。它其中的TTStext-to-speech)引擎可以用于实现语音合成,我们通过TTS引擎可以分析文本内容并且将其朗读出。实现TTS技术的方法有很多种,现在主要采用三种:连词技术、语音合成技术、子字连接技术。目前的5.1版本的SDK一共可以支持3种语言的识别 (英语,汉语和日语)以及2种语言的合成(英语和汉语)。其中还包括对于低层控制和高度适应性的直接语音管理、训练向导、事件、语法编译、资源、语音识别(SR)管理以及TTS管理等强大的设计接口。

 

 

2.   实现原理

 

     下是SpeechAPI的总体结构:

      

       从图中我们可以看出语音引擎则通过DDI层(设备驱动接口)和SAPI(SpeechAPI)进行交互,应用程序通过API层和SAPI通信。通过使用这些API,用户可以快速开发在语音识别或语音合成方面应用程序。

     应用程序使用ISpVoice接口来控制TTS,通过调用其中的Speak方法可以朗读出文本内容,通过调用SetVoice / GetVoice方法(在.NET中已经转变成Voice属性)来获取或设置朗读的语音,而通过调用GetVolume / SetVolumeGetRate / SetRate等方法(在.NET中已经转变成VolumeRate属性)来获取或设置朗读的音量和语速。

功能强大之处在于TTS能识别XML标记,通过给文本加上XML标记,我们让TTS朗读出更加符合语言阅读习惯的句子。例如:

l   <volume level=”60”></volume> 用于设置文本朗读的音量;

l   <rate absspeed=”1”/><rate speed=”5”/> 分别用于设置文本朗读的绝对速度和相对速度;

l   <pitch absmiddle=”2”/><pitch middle=”5”/> 分别用于设置文本朗读的绝对语调和相对语调;

l   <emph></emph> 在他们之间的句子被视为强调;

l   <spell></spell> 可以将单词逐个字母的拼写出来;

l   <silence msec=”500”/> 表示停止发声,并保持500微秒;

l   <context id=”date_mdy”>02/03/07</context> 可以按要求朗读出日期

l   <voice required="Language=409"></voice> 用于设置朗读所用的语言,其中409表示使用英语,804表示使用汉语,而411表示日语。

 

 

3.   软件的开发

 

3.1. 开发环境的搭建

 

由于Microsoft Speech SDK是以COM组件的形式提供给我们的,因此在使用.NET开发时必须引入Interop.SpeechLib.dll文件,如图:

 

 

在引入DLL文件后,我们就可以通过添加using SpeechLib;”引入命名空间,或直接使用SpeechLib前缀来使用SpeechLib空间下的所有类。

 

3.2.    二次封装TTS

 

我们将使用Singleton设计模式来对TTS进行封装,首先声明一个SpVoice接口,并用SpVoiceClass 对象来实例化,这个接口是实现文本朗读的核心。接着提供用于朗读文本的方法,例如:

  /// <summary>

        /// 读出Xml文件内容

        /// </summary>

        /// <param name="xml">Xml文件内容</param>

        public void SpeakXml(string xml)

        {

            voice.Speak(xml, SpeechVoiceSpeakFlags.SVSFIsXML | SpeechVoiceSpeakFlags.SVSFlagsAsync);

        }

并且使Pause()Resume()Stop()等方法来控制朗读暂停、继续和停止。至于保存音频文件,我们可以使用以下方法,将音频输出流指向一个文件流,来完成保存工作。

        /// <summary>

        /// 保存音频到文件

        /// </summary>

        /// <param name="xml">要读的Xml格式的内容</param>

        /// <param name="fileName">要保存的文件名</param>

        public void Save(string xml, string fileName)

        {

            SpFileStream stream = new SpFileStream();

            stream.Open(fileName, SpeechStreamFileMode.SSFMCreateForWrite, false);

            voice.AudioOutputStream = stream;

            voice.Speak(xml, SpeechVoiceSpeakFlags.SVSFlagsAsync | SpeechVoiceSpeakFlags.SVSFIsXML);

            voice.WaitUntilDone(Timeout.Infinite);

            stream.Close();

        }

 

 

3.3. 实现中英文的混合朗读

 

如果我们直接调用SpVoice接口中Speak方法来朗读文本,那么在朗读过程中,要么使用英文朗读引擎,要么使用中文朗读引擎,这样就只能朗读纯英文文本或纯中文文本。要怎样才能朗读混合的文本呢?第一种方法,我们可以在朗读过程中,根据文本的内容来切换朗读所用的引擎,即调用SetChinaVoice()SetEnglishVoice()方法。第二种方法,我们在朗读文本之前,先分析文本,将属于英文的句子加上英文语音XML标记,即<voice required=”Language=409”></voice>,将属于中文的句子加上中文语音XML记,即<voice required=”Language=804”></voice>。这样调用SpeakXml方法就可以实现中英文混合朗读。

在这里我选择第二种方法,在类中增加静态方法:AddXmlLangTag,返回添加过标记得文本内容。

/// <summary>

        /// 设置中文语音

        /// </summary>

        public void SetChinaVoice()

        {

            voice.Voice = voice.GetVoices(string.Empty, string.Empty).Item(0);

        }

 

        /// <summary>

        /// 设置英文语音

        /// </summary>

        public void SetEnglishVoice()

        {

            voice.Voice = voice.GetVoices(string.Empty, string.Empty).Item(1);

        }

 

 

3.4. 界面的实现

 

 

在打开文件时,可以选择打开文本文件(*.Txt)和XML文件(*.Xml),如果打开的是XML文件,将不对内容作任何修改,并且也不允许调节音量、语速、语调,因为这些都应该在XML文件中写好;如果打开的是文本文件,则在朗读前,会调用AddXmlLangTag方法给文本加上语言标记,调用AddXmlPitchTag方法给文本加上语调标记,同时也允许调节音量、语速、语调。

   

 

4.   总结

     通过为普通文本内容设置语音XML标记,并调用SpVoice接口的Speak方法,可以实现中英文文本的混合朗读。如果要使朗读的效果更佳,就必须手工为每一个句子设置相应的XML标记,这样可使朗读更接近人性化。

 

 

 

 

 

【参考文献】

1Microsoft Speech SDK 帮助 sapi.chm

2http://www.codeproject.com/vb/net/TTSinVBpackage.asp

3http://www.c-sharpcorner.com/SpeechNet.asp

4http://www.supinfo-projects.com/cn/2006/xing_jin_inter_2006/

5http://www.microsoft.com/china/community/program/originalarticles/TechDoc/Cnspeech.mspx

6http://www.microsoft.com/speech/default.mspx

 

【源码】

Talker.cs

  1. using System; 
  2. using System.Collections.Generic; 
  3. using System.Text; 
  4. using System.Threading; 
  5. using SpeechLib; 
  6.   
  7. namespace VoiceTalker 
  8.     /// <summary> 
  9.     /// TTS语音朗读类 
  10.     /// </summary> 
  11.     public class Talker 
  12.     { 
  13.         private static Talker talker = null;        //Talker对象 
  14.         private SpVoice voice = null;               //SpVoice对象,用于实现TTS 
  15.   
  16.         /// <summary> 
  17.         /// 默认的英文语音ID 
  18.         /// </summary> 
  19.         public const string DefaultEnglishLangID = "409"
  20.   
  21.         /// <summary> 
  22.         /// 默认的中文语音ID 
  23.         /// </summary> 
  24.         public const string DefaultChineseLangID = "804"
  25.   
  26.         /// <summary> 
  27.         /// 构造函数 
  28.         /// </summary> 
  29.         private Talker() 
  30.         { 
  31.             voice = new SpeechLib.SpVoiceClass(); 
  32.         } 
  33.   
  34.         /// <summary> 
  35.         /// 获取或设置音量 
  36.         /// </summary> 
  37.         public int Volume 
  38.         { 
  39.             get 
  40.             { 
  41.                 return voice.Volume; 
  42.             } 
  43.             set 
  44.             { 
  45.                 voice.Volume = value; 
  46.             } 
  47.         } 
  48.   
  49.         /// <summary> 
  50.         /// 获取或设置语速 
  51.         /// </summary> 
  52.         public int Rate 
  53.         { 
  54.             get 
  55.             { 
  56.                 return voice.Rate; 
  57.             } 
  58.             set 
  59.             { 
  60.                 voice.Rate = value; 
  61.             } 
  62.         } 
  63.   
  64.         /// <summary> 
  65.         /// 获取或设置语音 
  66.         /// </summary> 
  67.         public SpObjectToken Voice 
  68.         { 
  69.             get 
  70.             { 
  71.                 return voice.Voice; 
  72.             } 
  73.             set 
  74.             { 
  75.                 voice.Voice = value; 
  76.             } 
  77.         } 
  78.   
  79.   
  80.         /// <summary> 
  81.         /// 获得实例 
  82.         /// </summary> 
  83.         /// <returns>Talker对象</returns> 
  84.         public static Talker Instance() 
  85.         { 
  86.             if (talker == null
  87.             { 
  88.                 talker = new Talker(); 
  89.             } 
  90.             return talker; 
  91.         } 
  92.   
  93.         /// <summary> 
  94.         /// 给文本内容添加语言标记 
  95.         /// </summary> 
  96.         /// <param name="src">文本内容</param> 
  97.         /// <returns>经过语言标记的文本内容</returns> 
  98.         public static string AddXmlLangTag(string src) 
  99.         { 
  100.             return AddXmlLangTag(src, Talker.DefaultEnglishLangID, Talker.DefaultChineseLangID); 
  101.         } 
  102.   
  103.         /// <summary> 
  104.         /// 给文本内容添加语言标记 
  105.         /// </summary> 
  106.         /// <param name="src">文本内容</param> 
  107.         /// <param name="englishLangID">英文语音ID</param> 
  108.         /// <param name="chineseLangID">中文语音ID</param> 
  109.         /// <returns>经过语言标记的文本内容</returns> 
  110.         public static string AddXmlLangTag(string src, string englishLangID, string chineseLangID) 
  111.         { 
  112.             if (src.Length < 1) 
  113.             { 
  114.                 return ""
  115.             } 
  116.   
  117.             StringBuilder dest = new StringBuilder(); 
  118.             int startPos = 0, endPos = 0; 
  119.             bool isAscii = !(src[0] > 128); 
  120.             for (int i = 0; i < src.Length; i++) 
  121.             { 
  122.                 /* 判断每个字符是否为ASCII,如果是则加上<voice required="Language=englishLangID"></voice> 
  123.                  * 如果不是就加上<voice required="Language=chineseLangID></voice> 
  124.                  */ 
  125.                 if (src[i] > 128) 
  126.                 { 
  127.                     if (isAscii) 
  128.                     { 
  129.                         string sub = src.Substring(startPos, endPos - startPos); 
  130.                         dest.Append("<voice required=\"Language=" + englishLangID + "\">" + sub + "</voice>"); 
  131.                         startPos = endPos; 
  132.                     } 
  133.                     isAscii = false
  134.                     endPos++; 
  135.                 } 
  136.                 else 
  137.                 { 
  138.                     if (!isAscii) 
  139.                     { 
  140.                         string sub = src.Substring(startPos, endPos - startPos); 
  141.                         dest.Append("<voice required=\"Language=" + chineseLangID + "\">" + sub + "</voice>"); 
  142.                         startPos = endPos; 
  143.                     } 
  144.                     isAscii = true
  145.                     endPos++; 
  146.                 } 
  147.             } 
  148.             string r = src.Substring(startPos, endPos - startPos); 
  149.             string langID = isAscii == true ? englishLangID : chineseLangID; 
  150.             dest.Append("<voice required=\"Language=" + langID + "\">" + r + "</voice>"); 
  151.             return dest.ToString(); 
  152.         } 
  153.   
  154.         /// <summary> 
  155.         /// 给文本内容添加语调标记 
  156.         /// </summary> 
  157.         /// <param name="src">文本内容</param> 
  158.         /// <param name="pitch">语调</param> 
  159.         /// <returns>经过语调标记的文本内容</returns> 
  160.         public static string AddXmlPitchTag(string src, int pitch) 
  161.         { 
  162.             string pitchTag = "<pitch absmiddle=\"" + pitch.ToString() + "\"/>"
  163.             return pitchTag + src; 
  164.         } 
  165.   
  166.         /// <summary> 
  167.         /// 设置中文语音 
  168.         /// </summary> 
  169.         public void SetChinaVoice() 
  170.         { 
  171.             voice.Voice = voice.GetVoices(string.Empty, string.Empty).Item(0); 
  172.         } 
  173.   
  174.         /// <summary> 
  175.         /// 设置英文语音 
  176.         /// </summary> 
  177.         public void SetEnglishVoice() 
  178.         { 
  179.             voice.Voice = voice.GetVoices(string.Empty, string.Empty).Item(1); 
  180.         } 
  181.   
  182.         /// <summary> 
  183.         /// 读出文件内容 
  184.         /// </summary> 
  185.         /// <param name="text">文件内容</param> 
  186.         public void SpeakText(string text) 
  187.         { 
  188.             voice.Speak(text, SpeechVoiceSpeakFlags.SVSFDefault | SpeechVoiceSpeakFlags.SVSFlagsAsync); 
  189.         } 
  190.   
  191.         /// <summary> 
  192.         /// 读出Xml文件内容 
  193.         /// </summary> 
  194.         /// <param name="xml">Xml文件内容</param> 
  195.         public void SpeakXml(string xml) 
  196.         { 
  197.             voice.Speak(xml, SpeechVoiceSpeakFlags.SVSFIsXML | SpeechVoiceSpeakFlags.SVSFlagsAsync); 
  198.         } 
  199.   
  200.         /// <summary> 
  201.         /// 读出文件内容 
  202.         /// </summary> 
  203.         /// <param name="fileName">文件名</param> 
  204.         public void SpeakFile(string fileName) 
  205.         { 
  206.             voice.Speak(fileName, SpeechVoiceSpeakFlags.SVSFIsFilename | SpeechVoiceSpeakFlags.SVSFlagsAsync); 
  207.         } 
  208.   
  209.         /// <summary> 
  210.         /// 暂停 
  211.         /// </summary> 
  212.         public void Pause() 
  213.         { 
  214.             voice.Pause(); 
  215.         } 
  216.   
  217.         /// <summary> 
  218.         /// 继续 
  219.         /// </summary> 
  220.         public void Resume() 
  221.         { 
  222.             voice.Resume(); 
  223.         } 
  224.   
  225.         /// <summary> 
  226.         ///   停止说话 
  227.         /// </summary> 
  228.         public void Stop() 
  229.         { 
  230.             voice.Speak(string.Empty, SpeechVoiceSpeakFlags.SVSFPurgeBeforeSpeak); 
  231.         } 
  232.   
  233.         /// <summary> 
  234.         /// 保存音频到文件 
  235.         /// </summary> 
  236.         /// <param name="xml">要读的Xml格式的内容</param> 
  237.         /// <param name="fileName">要保存的文件名</param> 
  238.         public void Save(string xml, string fileName) 
  239.         { 
  240.             SpFileStream stream = new SpFileStream(); 
  241.             stream.Open(fileName, SpeechStreamFileMode.SSFMCreateForWrite, false); 
  242.             voice.AudioOutputStream = stream; 
  243.             voice.Speak(xml, SpeechVoiceSpeakFlags.SVSFlagsAsync | SpeechVoiceSpeakFlags.SVSFIsXML); 
  244.             voice.WaitUntilDone(Timeout.Infinite); 
  245.             stream.Close(); 
  246.         } 
  247.     } 

VoiceForm.cs

  1. using System; 
  2. using System.Collections.Generic; 
  3. using System.ComponentModel; 
  4. using System.Data; 
  5. using System.Drawing; 
  6. using System.Text; 
  7. using System.IO; 
  8. using System.Windows.Forms; 
  9.   
  10. namespace VoiceTalker 
  11.     public partial class VoiceForm : Form 
  12.     { 
  13.         private bool isXml = false
  14.         private Talker talker = Talker.Instance(); 
  15.   
  16.         public VoiceForm() 
  17.         { 
  18.             InitializeComponent(); 
  19.         } 
  20.   
  21.         private void openButton_Click(object sender, EventArgs e) 
  22.         { 
  23.             DialogResult dr = openFileDialog.ShowDialog(); 
  24.             if (dr == DialogResult.OK) 
  25.             { 
  26.                 FileInfo fi = new FileInfo(openFileDialog.FileName); 
  27.                 //判断是否为Xml格式的文件 
  28.                 if (fi.Extension.ToLower() == ".xml"
  29.                 { 
  30.                     controlBox.Enabled = false
  31.                     isXml = true
  32.                 } 
  33.                 else 
  34.                 { 
  35.                     controlBox.Enabled = true
  36.                 } 
  37.                 fileNameText.Text = fi.FullName; 
  38.                 contentText.Text = ""
  39.   
  40.                 //读取文件内容 
  41.                 StreamReader sr = new StreamReader(openFileDialog.OpenFile(), Encoding.Default); 
  42.                 contentText.Text = sr.ReadToEnd(); 
  43.             } 
  44.         } 
  45.   
  46.         private void speakButton_Click(object sender, EventArgs e) 
  47.         { 
  48.             try 
  49.             { 
  50.                 if (isXml) 
  51.                 { 
  52.                     talker.SpeakXml(contentText.Text); 
  53.                 } 
  54.                 else 
  55.                 { 
  56.                     talker.Volume = volumeBar.Value; 
  57.                     talker.Rate = rateBar.Value; 
  58.                     String readText = Talker.AddXmlLangTag(contentText.Text); 
  59.                     readText = Talker.AddXmlPitchTag(readText, pitchBar.Value); 
  60.                     talker.SpeakXml(readText); 
  61.                 } 
  62.             } 
  63.             catch (Exception ex) 
  64.             { 
  65.                 MessageBox.Show(ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 
  66.             } 
  67.         } 
  68.   
  69.         private void StopButton_Click(object sender, EventArgs e) 
  70.         { 
  71.             try 
  72.             { 
  73.                 talker.Stop(); 
  74.             } 
  75.             catch (Exception ex) 
  76.             { 
  77.                 MessageBox.Show(ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 
  78.             } 
  79.         } 
  80.   
  81.         private void pauseButton_Click(object sender, EventArgs e) 
  82.         { 
  83.             try 
  84.             { 
  85.                 if (pauseButton.Text == "暂停"
  86.                 { 
  87.                     talker.Pause(); 
  88.                     pauseButton.Text = "继续"
  89.                 } 
  90.                 else 
  91.                 { 
  92.                     talker.Resume(); 
  93.                     pauseButton.Text = "暂停"
  94.                 } 
  95.             } 
  96.             catch (Exception ex) 
  97.             { 
  98.                 MessageBox.Show(ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 
  99.             } 
  100.         } 
  101.   
  102.         private void saveButton_Click(object sender, EventArgs e) 
  103.         { 
  104.             try 
  105.             { 
  106.                 DialogResult dr = saveFileDialog.ShowDialog(); 
  107.                 if (dr == DialogResult.OK) 
  108.                 { 
  109.                     string fileName = saveFileDialog.FileName; 
  110.                     string content = contentText.Text; 
  111.                     if (!isXml) 
  112.                     { 
  113.                         talker.Volume = volumeBar.Value; 
  114.                         talker.Rate = rateBar.Value; 
  115.                         content = Talker.AddXmlLangTag(content); 
  116.                         content = Talker.AddXmlPitchTag(content, pitchBar.Value); 
  117.                     } 
  118.                     talker.Save(content, fileName); 
  119.                 } 
  120.             } 
  121.             catch (Exception ex) 
  122.             { 
  123.                 MessageBox.Show(ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 
  124.             } 
  125.         } 
  126.   
  127.         private void volumeBar_Scroll(object sender, EventArgs e) 
  128.         { 
  129.             try 
  130.             { 
  131.                 talker.Volume = volumeBar.Value; 
  132.             } 
  133.             catch (Exception ex) 
  134.             { 
  135.                 MessageBox.Show(ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 
  136.             } 
  137.         } 
  138.   
  139.         private void rateBar_Scroll(object sender, EventArgs e) 
  140.         { 
  141.             try 
  142.             { 
  143.                 talker.Rate = rateBar.Value; 
  144.             } 
  145.             catch (Exception ex) 
  146.             { 
  147.                 MessageBox.Show(ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 
  148.             } 
  149.         } 
  150.   
  151.         private void aboutButton_Click(object sender, EventArgs e) 
  152.         { 
  153.             new AboutForm().ShowDialog(); 
  154.         } 
  155.   
  156.         private void exitButton_Click(object sender, EventArgs e) 
  157.         { 
  158.             Application.Exit(); 
  159.         } 
  160.     } 

出处:http://tb.blog.csdn.net/TrackBack.aspx?PostId=1529844