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