微軟SAPI讓你的軟件能說(shuō)會(huì)道
2011/01/06
“沒(méi)聲音,再好的戲也出不來(lái)。”這雖然是一句廣告,但是也說(shuō)出了一個(gè)道理,我們所開(kāi)發(fā)的軟件,特別是一些多媒體軟件,要是能夠發(fā)出聲音,能說(shuō)會(huì)道,將為我們的軟件增添不少光彩。同時(shí),我們面臨的是一個(gè)老齡化的社會(huì),將會(huì)有越來(lái)越多的視力不太好的老年人成為我們的用戶,開(kāi)始使用我們的軟件,如果我們的軟件能說(shuō)會(huì)道,可以用語(yǔ)音的方式提示用戶進(jìn)行操作,這將大大增加軟件的可用性,從而獲得用戶的喜愛(ài)。// SpVoice對(duì)象,我們將使用這個(gè)對(duì)象來(lái)朗讀文本 private SpVoice m_spVoice; private void Init() { // 創(chuàng)建SpVoice對(duì)象 m_spVoice = new SpVoice(); // 枚舉出系統(tǒng)中已經(jīng)安裝的語(yǔ)音,并將其填充到Combo Box控件中 foreach (ISpeechObjectToken Token in m_spVoice.GetVoices(string.Empty, string.Empty)) { this.cmbVoices.Items.Add(Token.GetDescription(49)); } // 默認(rèn)選中第一個(gè)語(yǔ)音 cmbVoices.SelectedIndex = 0; } |
朗讀文本
完成窗體的初始化,創(chuàng)建SpVoice對(duì)象之后,接下來(lái)我們就可以利用這個(gè)對(duì)象的Speak()方法來(lái)閱讀Text Box控件中的文本了。
private void btnSpeak_Click(object sender, EventArgs
e) { // 獲取用戶在Combo Box中選擇的語(yǔ)音索引 int nVoiceIndex = this.cmbVoices.SelectedIndex; // 根據(jù)語(yǔ)音索引指定SpVoice的Voice屬性,也就是指定使用何種語(yǔ)音 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對(duì)象的一個(gè)最重要的函數(shù)Speak(),它的第一個(gè)參數(shù)就是我們要朗讀的文本,而第二個(gè)參數(shù)則是朗讀的方式,有同步,異步,XML文件等等。
這樣,通過(guò)SpVoice對(duì)象的一個(gè)簡(jiǎn)單函數(shù),我們就可以朗讀Text Box控件中的文本內(nèi)容了。
朗讀文本文件
更多時(shí)候,我們不是閱讀Text Box控件中輸入的文本,而是閱讀某些文本文件中的文字,這樣,讀取文本文件并將文字填充到Text Box控件中就成為一種必要了。
private void btnFileSelect_Click(object sender, EventArgs
e) { // 使用打開(kāi)文件對(duì)話框選擇文本文件 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; } } // 將文本文件中的內(nèi)容顯示到Text Box控件 this.textPreview.Text = sPreview; // 關(guān)閉文件讀取器 objReader.Close(); } } 、 |
這樣,我們就可以通過(guò)讀取文本文件中的內(nèi)容,將其顯示到Text Box控件中,然后SpVoice就可以閱讀Text Box控件中的內(nèi)容,也就是間接地朗讀了文本文件。
將文本轉(zhuǎn)換成聲音文件
除了直接朗讀文本之外,更多的時(shí)候,我們還需要將文本轉(zhuǎn)換成聲音文件。這樣我們可以將這些聲音文件隨身攜帶,想聽(tīng)就聽(tīng)。要將文本轉(zhuǎn)換為聲音文件,我們需要用到SpVoice的另外一個(gè)重要的函數(shù)SetOutput(),我們可以利用它將SpVoice的語(yǔ)音輸出某個(gè)WAV文件,從而實(shí)現(xiàn)將文本文件轉(zhuǎn)換為聲音文件。
因?yàn)?將一段比較長(zhǎng)的文本轉(zhuǎn)換成聲音文件,通常是一個(gè)比較長(zhǎng)的過(guò)程,所以在這里我們創(chuàng)建一個(gè)專門(mén)的工作者線程來(lái)負(fù)責(zé)文本的轉(zhuǎn)換,而界面線程則負(fù)責(zé)顯示轉(zhuǎn)換的進(jìn)度。
// 工作者線程類 public class WorkerThread { // 用戶選擇的語(yǔ)音 private int nVoiceIndex; // 保存的文件名 private string strFileName; // 需要轉(zhuǎn)換的文本 private ArrayList arrText; // 構(gòu)造函數(shù),利用構(gòu)造函數(shù)向線程傳遞參數(shù) public WorkerThread(int nIndex, ArrayList aText, string sFileName ) { nVoiceIndex = nIndex; arrText = aText; strFileName = sFileName; } // 線程開(kāi)始事件 public event EventHandler threadStartEvent; // 線程執(zhí)行時(shí)的事件 public event EventHandler threadEvent; // 線程結(jié)束事件 public event EventHandler threadEndEvent; // 線程函數(shù) public void runMethod() { // 創(chuàng)建SpVoice對(duì)象,并選擇用戶選中的語(yǔ)音 SpVoice voice = new SpVoice(); voice.Voice = voice.GetVoices(string.Empty, string.Empty).Item(nVoiceIndex); try { // 創(chuàng)建流媒體文件 SpeechStreamFileMode SpFileMode = SpeechStreamFileMode.SSFMCreateForWrite; SpFileStream SpFileStream = new SpFileStream(); // 這里我們?cè)O(shè)置輸出的頻率,這樣可以決定輸出文件的大小 SpFileStream.Format.Type = SpeechAudioFormatType.SAFTCCITT_ALaw_8kHzMono; // 還可以選擇更高品質(zhì)的格式,不過(guò)產(chǎn)生的文件體積更大 // SpFileStream.Format.Type = SpeechAudioFormatType.SAFT11kHz16BitMono; // 創(chuàng)建文件,并將SpVoice的輸出流指定為當(dāng)前文件 SpFileStream.Open(strFileName, SpFileMode, false); voice.AudioOutputStream = SpFileStream; // 發(fā)送線程開(kāi)始事件,通知主界面,設(shè)定進(jìn)度條的最大值為Count threadStartEvent.Invoke(arrText.Count, new EventArgs()); // 開(kāi)始將文本輸出到音頻文件 int nCount = 0; foreach (string sOutput in arrText) { voice.Speak(sOutput, SpeechVoiceSpeakFlags.SVSFlagsAsync); // 發(fā)送線程運(yùn)行時(shí)事件,移動(dòng)進(jìn)度條的位置 threadEvent.Invoke(nCount, new EventArgs()); voice.WaitUntilDone(-1); ++nCount; } // 關(guān)閉音頻文件 SpFileStream.Close(); } catch { } // 發(fā)送線程結(jié)束事件,通知主界面關(guān)閉進(jìn)度條 threadEndEvent.Invoke(new object(), new EventArgs()); } } |
跟直接朗讀文本相似,我們?nèi)耘f使用SpVoice的Speak()函數(shù)朗讀文本,只是我們通過(guò)指定SpVoice的AudioOutputStream屬性,將語(yǔ)音輸出到一個(gè)音頻文件,這樣就完成了文本文件到音頻文件的轉(zhuǎn)換。
完成轉(zhuǎn)換工作者線程的創(chuàng)建后,我們就可以利用它來(lái)完成具體的轉(zhuǎn)換工作。在窗體的保存按鈕的單擊響應(yīng)函數(shù)中,我們創(chuàng)建相應(yīng)的工作者線程來(lái)進(jìn)行文本的轉(zhuǎn)換。
private void btnSavetoWAV_Click(object sender, EventArgs
e) { string strWAVFile = ""; try { // 使用保存文件對(duì)話框,選擇保存的文件 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控件獲取要轉(zhuǎn)換的文本 ArrayList arrText = new ArrayList(); foreach (String sLine in this.textPreview.Lines) arrText.Add(sLine); // 顯示進(jìn)度條 progressForm = new Form2(); progressForm.Show(); // 創(chuàng)建工作者線程,并向工作者線程傳遞要轉(zhuǎn)換的文本 WorkerThread myThreadFun = new WorkerThread( this.cmbVoices.SelectedIndex, arrText, strWAVFile); // 注冊(cè)線程事件 myThreadFun.threadStartEvent += new EventHandler(method_threadStartEvent); myThreadFun.threadEvent += new EventHandler(method_threadEvent); myThreadFun.threadEndEvent += new EventHandler(method_threadEndEvent); // 創(chuàng)建線程,執(zhí)行工作者線程 Thread thread = new Thread(new ThreadStart(myThreadFun.runMethod)); // 啟動(dòng)線程 thread.Start(); } } catch { } } |
除了創(chuàng)建線程進(jìn)行文本的轉(zhuǎn)換之外,為了讓我們的軟件更加易用,更加人性化,我們還需要響應(yīng)線程事件,移動(dòng)進(jìn)度條的位置以反映轉(zhuǎn)換的進(jìn)度,免得用戶以為軟件在比較長(zhǎng)的轉(zhuǎn)換過(guò)程中死掉了。
// 線程開(kāi)始的時(shí)候調(diào)用的委托 private delegate void maxValueDelegate(int maxValue); // 線程執(zhí)行中調(diào)用的委托 private delegate void nowValueDelegate(int nowValue); // 線程結(jié)束的時(shí)候調(diào)用的委托 private delegate void hideProgressDelegate(int n); /// 線程完成事件,隱藏進(jìn)度條窗口 /// 但是我們不能直接操作進(jìn)度條,需要一個(gè)委托來(lái)替我們完成 void method_threadEndEvent(object sender, EventArgs e) { hideProgressDelegate hide = new hideProgressDelegate(hideProgress); this.Invoke(hide, 0); } /// 線程執(zhí)行中的事件,設(shè)置進(jìn)度條當(dāng)前進(jìn)度 /// 這里的sender,是WorkerThread 函數(shù)中傳過(guò)來(lái)的當(dāng)前值 void method_threadEvent(object sender, EventArgs e) { int nowValue = Convert.ToInt32(sender); nowValueDelegate now = new nowValueDelegate(setNow); this.Invoke(now, nowValue); } /// 線程開(kāi)始事件,設(shè)置進(jìn)度條最大值 /// 但是我不能直接操作進(jìn)度條,需要一個(gè)委托來(lái)替我完成 /// 這里的sender,是WorkerThread 函數(shù)中傳過(guò)來(lái)的最大值 void method_threadStartEvent(object sender, EventArgs e) { int maxValue = Convert.ToInt32(sender); maxValueDelegate max = new maxValueDelegate(setMax); this.Invoke(max, maxValue); } /// 被委托調(diào)用的函數(shù),專門(mén)操作進(jìn)度條 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的閱讀
到這里,一個(gè)能說(shuō)會(huì)道的軟件基本上已經(jīng)完成了,但是,為了讓我們的軟件更加易用,我們還可以通過(guò)SpVoice提供的函數(shù)對(duì)SpVoice的行為進(jìn)行控制,讓她更加符合我們的心意。例如,我們可以控制SpVoice的暫停和繼續(xù)。
private void btnPause_Click(object sender, EventArgs
e) { if (this.btnPause.Text == "暫停") { // 讓SpVoice暫停朗讀 m_spVoice.Pause(); this.btnPause.Text ="繼續(xù)"; } else { // 讓SpVoice繼續(xù)朗讀 m_spVoice.Resume(); this.btnPause.Text = "暫停"; } } |
IT168
微軟Mediaroom平臺(tái):實(shí)現(xiàn)電信級(jí)的電視服務(wù) 2010-12-29 |
徐工集團(tuán)重構(gòu)CRM提升企業(yè)客戶關(guān)系管理 2010-12-22 |
微軟發(fā)布新版iPhone客戶端——必應(yīng)2.0 2010-12-17 |
微軟Lync整合統(tǒng)一通信 和辰信息順勢(shì)而為 2010-12-15 |
2011年:微軟計(jì)劃在CRM領(lǐng)域超越Salesforce 2010-12-14 |