技术开发 频道

微软SAPI:让你的软件能说会道

  【IT168专稿】“没声音,再好的戏也出不来。”这虽然是一句广告,但是也说出了一个道理,我们所开发的软件,特别是一些多媒体软件,要是能够发出声音,能说会道,将为我们的软件增添不少光彩。同时,我们面临的是一个老龄化的社会,将会有越来越多的视力不太好的老年人成为我们的用户,开始使用我们的软件,如果我们的软件能说会道,可以用语音的方式提示用户进行操作,这将大大增加软件的可用性,从而获得用户的喜爱。

  那么如何才能让我们的软件能说会道呢?别着急,微软有解决办法,用微软提供的SAPI就可以让我们的软件能说会道。

  什么是SAPI?

  软件中的语音技术主要包括两方面的内容,一个是语音识别(speech recognition) ,另外一个是语音合成(speech synthesis),也即是文本语音转换系统(TTS)。TTS系统使用合成语音合成文本字符串和文件到声音音频流。而语音识别系统则是转换人类的声音语音流到可读的文本字符串或者文件。这两个工作,都是通过各种语音引擎来完成的。微软所提供的SAPI (全称The Microsoft Speech API),正是在应用程序和语音引擎之间提供一个高级别的接口,它实现了所有必需的对各种语音引擎的实时的控制和管理等低级别的细节。语音引擎通过DDI层(设备驱动接口)和SAPI进行交互,应用程序通过API层和SAPI通信。通过使用这些API,我们可以快速开发在语音识别或语音合成方面应用程序。SAPI 应用程序编程接口(API)明显的减少了构建一个使用语音识别和文本语音转换的应用程序所需要的高层代码,使语音技术更加容易使用并且更加扩大了应用的范围。虽然现在SAPI不是业界标准,但是应用非常广泛。

  SAPI包括以下组件对象(接口):

  (1)Voice Commands API。对应用程序进行控制,一般用于语音识别系统中。识别某个命令后,会调用相关接口是应用程序完成对应的功能。如果程序想实现语音控制,必须使用此组对象。

  (2)Voice Dictation API。听写输入,即语音识别接口。

  (3)Voice Text API。完成从文字到语音的转换,即语音合成。

  (4)Voice Telephone API。语音识别和语音合成综合运用到电话系统之上,利用此接口可以建立一个电话应答系统,甚至可以通过电话控制计算机。

  (5)Audio Objects API。封装了计算机发音系统。

  其中Voice Text API,就是微软TTS引擎的接口,通过它我们可以很容易地建立功能强大的文本语音程序,金山词霸的单词朗读功能就用到了这些API,而目前几乎所有的文本朗读工具都是用SAPI开发的。在这里,我们使用的主要就是Voice Text API。

  安装SAPI SDK

  要使用SAPI让我们的软件能说会道,我们首先需要下载并安装SAPI SDK。首先从微软的网站上下载开发包: http://www.microsoft.com/speech/download/sdk51

  下载完毕后,首先安装SpeechSDK51.exe,然后安装中文语言补丁包SpeechSDK51LangPack,If 如果我们想将SAPI作为我们软件的一部分,随着我们的软件重新发布,我们还需要安装SpeechSDK51MSM.exe。

  安装好SAPI SDK后,即可开始在VS2010中使用SAPI让我们的软件能说会道了。

  创建项目,添加SAPI的引用

  这里,我们将创建一个普通的WinForm程序,它可以利用TTS朗读文本,也可以将文本文件通过TTS转换为声音文件,真正是一个“能说会道”的软件。首先,我们在VS2010中创建一个WinForm程序,并且将窗体设计如下:

  中的Text Box用于显示我们要阅读的文本,而Combo Box控件用于显示系统中已经安装的所有语音,用户可以从中选择当前TTS使用的语音。

  要在我们的项目中使用SAPI,我们还需要给项目添加SAPI的引用。用VS2010提供的添加引用功能,在添加引用对话框的COM标签页面中找到Microsoft Speech Object Library,将其添加到项目中。

  SAPI所提供的各个类都在名字空间SpeechLib之下,所以在代码中我们还需要使用using SpeechLib,表示我们将使用这个名字空间。这样我们就可以使用SAPI所提供的各种类来实现语音合成或者是语音识别了。

  创建SpVoice对象,初始化SAPI

  SAPI的TTS都是通过SpVoice对象来完成的。SpVoice类是支持语音合成(TTS)的核心类。通过SpVoice对象调用TTS引擎,从而实现朗读功能。 SpVoice类有以下主要属性:

  • Voice:表示发音类型,相当于进行朗读的人,通常我们可以通过安装相应的语音引擎来增加相应的语音。

  • Rate:语音朗读速度,取值范围为-10到+10。数值越大,速度越快。

  • Volume:音量,取值范围为0到100。数值越大,音量越大。

  SpVoice有以下主要方法:

  • Speak():完成将文本信息转换为语音并按照指定的参数进行朗读,该方法有Text和Flags两个参数,分别指定要朗读的文本和朗读方式(同步或异步等)。

  • GetVoices():获取系统中的语音,用于指定SpVoice的Voice属性。

  • Pause():暂停使用该对象的所有朗读进程。该方法没有参数。

  • Resume():恢复该对象所对应的被暂停的朗读进程。该方法没有参数。

  所以我们在窗体的构造函数中,首先需要完成SpVoice对象的创建,然后才能使用这个对象来朗读文本。 因为系统中可能有多个语音可供选择,所以我们在创建窗体的时候,同时需要用一个Combo Box控件列举出系统中所有的语音,并且选中默认的第一个语音。当窗体创建后,用户可以在这个Combo Box选择自己喜欢的语音来朗读文本。

// SpVoice对象,我们将使用这个对象来朗读文本
private SpVoice m_spVoice;

        
private void Init()
        {
        
// 创建SpVoice对象    
            m_spVoice
= new SpVoice();
            
// 枚举出系统中已经安装的语音,并将其填充到Combo Box控件中
     foreach (ISpeechObjectToken Token in m_spVoice.GetVoices(
string.Empty, string.Empty))
            {
                this.cmbVoices.Items.Add(Token.GetDescription(
49));
            }
        
// 默认选中第一个语音
            cmbVoices.SelectedIndex
= 0;
        }

  朗读文本

  完成窗体的初始化,创建SpVoice对象之后,接下来我们就可以利用这个对象的Speak()方法来阅读Text Box控件中的文本了。

private void btnSpeak_Click(object sender, EventArgs e)
        {
        
// 获取用户在Combo Box中选择的语音索引
            
int nVoiceIndex = this.cmbVoices.SelectedIndex;
          
// 根据语音索引指定SpVoice的Voice属性,也就是指定使用何种语音
    m_spVoice.Voice
= m_spVoice.GetVoices(string.Empty, string.Empty).Item(nVoiceIndex);
          
// 使用SpVoice的Speak()方法阅读Text Box中文本
            m_spVoice.Speak(this.textPreview.Text, SpeechVoiceSpeakFlags.SVSFlagsAsync);
        }

  在这里我们使用了SpVoice对象的一个最重要的函数Speak(),它的第一个参数就是我们要朗读的文本,而第二个参数则是朗读的方式,有同步,异步,XML文件等等。 这样,通过SpVoice对象的一个简单函数,我们就可以朗读Text Box控件中的文本内容了。

  朗读文本文件

  更多时候,我们不是阅读Text Box控件中输入的文本,而是阅读某些文本文件中的文字,这样,读取文本文件并将文字填充到Text Box控件中就成为一种必要了。

private void btnFileSelect_Click(object sender, EventArgs e)
        {
            
// 使用打开文件对话框选择文本文件
            OpenFileDialog openFileDialog1
= new OpenFileDialog();

            openFileDialog1.InitialDirectory
= "e:\\";
            openFileDialog1.Filter
= "txt files (*.txt)|*.txt|All files (*.*)|*.*";
            openFileDialog1.FilterIndex
= 2;
            openFileDialog1.RestoreDirectory
= true;

            
if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
            
// 读取文本文件并将其填充到Text Box控件
                StreamReader objReader
= new StreamReader( openFileDialog1.FileName);
                
string sLine = "";
                
string sPreview = "";

                
while (sLine != null)
                {
                    sLine
= objReader.ReadLine();
                    
if (sLine != null)
                    {
            
// 这里需要添加Environment.NewLine表示换行
                        sPreview
+= sLine + Environment.NewLine;
                    }
                }
                
// 将文本文件中的内容显示到Text Box控件
                this.textPreview.Text
= sPreview;
                
// 关闭文件读取器
         objReader.Close();
            }
        }

  这样,我们就可以通过读取文本文件中的内容,将其显示到Text Box控件中,然后SpVoice就可以阅读Text Box控件中的内容,也就是间接地朗读了文本文件。

  将文本转换成声音文件

  除了直接朗读文本之外,更多的时候,我们还需要将文本转换成声音文件。这样我们可以将这些声音文件随身携带,想听就听。要将文本转换为声音文件,我们需要用到SpVoice的另外一个重要的函数SetOutput(),我们可以利用它将SpVoice的语音输出某个WAV文件,从而实现将文本文件转换为声音文件。

  因为 将一段比较长的文本转换成声音文件,通常是一个比较长的过程,所以在这里我们创建一个专门的工作者线程来负责文本的转换,而界面线程则负责显示转换的进度。

// 工作者线程类
    
public class WorkerThread
    {
        
// 用户选择的语音
        
private int nVoiceIndex;
        
// 保存的文件名
        
private string strFileName;
        
// 需要转换的文本
        
private ArrayList arrText;
        
        
// 构造函数,利用构造函数向线程传递参数
        
public WorkerThread(int nIndex, ArrayList aText, string sFileName )
        {
            nVoiceIndex
= nIndex;
            arrText
= aText;
            strFileName
= sFileName;
        }

        
// 线程开始事件
        
public event EventHandler threadStartEvent;
        
// 线程执行时的事件
        
public event EventHandler threadEvent;
        
// 线程结束事件
        
public event EventHandler threadEndEvent;

        
// 线程函数
        
public void runMethod()
        {
            
// 创建SpVoice对象,并选择用户选中的语音
            SpVoice voice
= new SpVoice();
            voice.Voice
= voice.GetVoices(string.Empty, string.Empty).Item(nVoiceIndex);
            try
            {
                
// 创建流媒体文件
                SpeechStreamFileMode SpFileMode
=
                       SpeechStreamFileMode.SSFMCreateForWrite;
                SpFileStream SpFileStream
= new SpFileStream();
                
// 这里我们设置输出的频率,这样可以决定输出文件的大小
                SpFileStream.Format.Type
= SpeechAudioFormatType.SAFTCCITT_ALaw_8kHzMono;
                
// 还可以选择更高品质的格式,不过产生的文件体积更大
            
// SpFileStream.Format.Type = SpeechAudioFormatType.SAFT11kHz16BitMono;

                
// 创建文件,并将SpVoice的输出流指定为当前文件
                SpFileStream.Open(strFileName, SpFileMode,
false);
                voice.AudioOutputStream
= SpFileStream;

                
// 发送线程开始事件,通知主界面,设定进度条的最大值为Count
                threadStartEvent.Invoke(arrText.Count,
new EventArgs());  
              
// 开始将文本输出到音频文件
                
int nCount = 0;
                foreach (
string sOutput in arrText)
                {
                    voice.Speak(sOutput, SpeechVoiceSpeakFlags.SVSFlagsAsync);
                    
// 发送线程运行时事件,移动进度条的位置
                    threadEvent.Invoke(nCount,
new EventArgs());
                    voice.WaitUntilDone(
-1);
                    
++nCount;
                }
                
                
// 关闭音频文件
                SpFileStream.Close();
            }
            catch
            {
            }
            
// 发送线程结束事件,通知主界面关闭进度条
            threadEndEvent.Invoke(
new object(), new EventArgs());
        }
    }

  跟直接朗读文本相似,我们仍旧使用SpVoice的Speak()函数朗读文本,只是我们通过指定SpVoice的AudioOutputStream属性,将语音输出到一个音频文件,这样就完成了文本文件到音频文件的转换。

  完成转换工作者线程的创建后,我们就可以利用它来完成具体的转换工作。在窗体的保存按钮的单击响应函数中,我们创建相应的工作者线程来进行文本的转换。

private void btnSavetoWAV_Click(object sender, EventArgs e)
        {
            
string strWAVFile = "";
            try
            {
                
// 使用保存文件对话框,选择保存的文件
                SaveFileDialog sfd
= new SaveFileDialog();
                sfd.Filter
= "All files (*.*)|*.*|wav files (*.wav)|*.wav";
                sfd.Title
= "Save to a wave file";
                sfd.FilterIndex
= 2;
                sfd.RestoreDirectory
= true;

                
if (sfd.ShowDialog() == DialogResult.OK)
                {
                    
// 获取用户输入的文件名
                    strWAVFile
= sfd.FileName;

                    
// 从Text Box控件获取要转换的文本
                    ArrayList arrText
= new ArrayList();
                    foreach (
String sLine in this.textPreview.Lines)
                        arrText.Add(sLine);

                    
// 显示进度条
                    progressForm
= new Form2();
                    progressForm.Show();

                    
// 创建工作者线程,并向工作者线程传递要转换的文本
                    WorkerThread myThreadFun
= new WorkerThread(
this.cmbVoices.SelectedIndex, arrText, strWAVFile);
                    
// 注册线程事件
                    myThreadFun.threadStartEvent
+= new EventHandler(method_threadStartEvent);
                    myThreadFun.threadEvent
+= new EventHandler(method_threadEvent);
                    myThreadFun.threadEndEvent
+= new EventHandler(method_threadEndEvent);
                    
// 创建线程,执行工作者线程
                    Thread thread
= new Thread(new ThreadStart(myThreadFun.runMethod));
                    
// 启动线程
                    thread.Start();
                }
            }
            catch
            {
            }
        }

  除了创建线程进行文本的转换之外,为了让我们的软件更加易用,更加人性化,我们还需要响应线程事件,移动进度条的位置以反映转换的进度,免得用户以为软件在比较长的转换过程中死掉了。

// 线程开始的时候调用的委托
        
private delegate void maxValueDelegate(int maxValue);
        
// 线程执行中调用的委托
        
private delegate void nowValueDelegate(int nowValue);
        
// 线程结束的时候调用的委托
        
private delegate void hideProgressDelegate(int n);
        
        
/// 线程完成事件,隐藏进度条窗口
      
/// 但是我们不能直接操作进度条,需要一个委托来替我们完成
        void method_threadEndEvent(
object sender, EventArgs e)
        {
            hideProgressDelegate hide
= new hideProgressDelegate(hideProgress);
            this.Invoke(hide,
0);
        }

        
/// 线程执行中的事件,设置进度条当前进度
        
/// 这里的sender,是WorkerThread 函数中传过来的当前值
        void method_threadEvent(
object sender, EventArgs e)
        {
            
int nowValue = Convert.ToInt32(sender);
            nowValueDelegate
now = new nowValueDelegate(setNow);
            this.Invoke(
now, nowValue);
        }
        
        
/// 线程开始事件,设置进度条最大值
        
/// 但是我不能直接操作进度条,需要一个委托来替我完成
        
///  这里的sender,是WorkerThread 函数中传过来的最大值
        void method_threadStartEvent(
object sender, EventArgs e)
        {
            
int maxValue = Convert.ToInt32(sender);
            maxValueDelegate max
= new maxValueDelegate(setMax);
            this.Invoke(max, maxValue);
        }

        
///  被委托调用的函数,专门操作进度条
        
private void setMax(int maxValue)
        {
            progressForm.progressBar1.Maximum
= maxValue;
        }
        
private void setNow(int nowValue)
        {
            progressForm.progressBar1.Value
= nowValue;
        }
        
private void hideProgress(int n)
        {
            progressForm.Hide();
        }

  控制SpVoice的阅读

  到这里,一个能说会道的软件基本上已经完成了,但是,为了让我们的软件更加易用,我们还可以通过SpVoice提供的函数对SpVoice的行为进行控制,让她更加符合我们的心意。例如,我们可以控制SpVoice的暂停和继续。

private void btnPause_Click(object sender, EventArgs e)
        {
            
if (this.btnPause.Text == "暂停")
            {
                
// 让SpVoice暂停朗读
                m_spVoice.Pause();
                this.btnPause.Text
="继续";
            }
            
else
            {
                  
// 让SpVoice继续朗读
                m_spVoice.Resume();
                this.btnPause.Text
= "暂停";
            }
        }

  通过SpVoice提供的函数,对SpVoice的行为进行控制就是这么简单。除了阅读的暂停和继续之外,我们还可以通过SetRate()函数设置声音的语调,通过SetVolume()函数设置声音的音量等等。这些函数就不在这里一一介绍了,留给大家自己去尝试。

  现在,使用SAPI,即刻让你的软件能说会道。

0
相关文章