微软SAPI让你的软件能说会道
2011/01/06
“没声音,再好的戏也出不来。”这虽然是一句广告,但是也说出了一个道理,我们所开发的软件,特别是一些多媒体软件,要是能够发出声音,能说会道,将为我们的软件增添不少光彩。同时,我们面临的是一个老龄化的社会,将会有越来越多的视力不太好的老年人成为我们的用户,开始使用我们的软件,如果我们的软件能说会道,可以用语音的方式提示用户进行操作,这将大大增加软件的可用性,从而获得用户的喜爱。// 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 = "暂停"; } } |
IT168
微软Mediaroom平台:实现电信级的电视服务 2010-12-29 |
徐工集团重构CRM提升企业客户关系管理 2010-12-22 |
微软发布新版iPhone客户端——必应2.0 2010-12-17 |
微软Lync整合统一通信 和辰信息顺势而为 2010-12-15 |
2011年:微软计划在CRM领域超越Salesforce 2010-12-14 |