Microsoft Speech SDK語(yǔ)音處理編程接口_第1頁(yè)
Microsoft Speech SDK語(yǔ)音處理編程接口_第2頁(yè)
Microsoft Speech SDK語(yǔ)音處理編程接口_第3頁(yè)
Microsoft Speech SDK語(yǔ)音處理編程接口_第4頁(yè)
Microsoft Speech SDK語(yǔ)音處理編程接口_第5頁(yè)
已閱讀5頁(yè),還剩46頁(yè)未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

MicrosoftSpeechSDK提供關(guān)于語(yǔ)音(Speech)處理的一套應(yīng)用程序編程接口SAPI(SpeechApplicationProgrammingInterface)。SAPI提供了實(shí)現(xiàn)文字-語(yǔ)音轉(zhuǎn)換(Text-to-Speech)和語(yǔ)音識(shí)別(SpeechRecognition)程序的基本函數(shù),大大簡(jiǎn)化了語(yǔ)音編程的難度,降低了語(yǔ)音編程的工作量。SpeechSDK可以免費(fèi)從如下網(wǎng)址下載:/speech。由于SpeechSDK是以COM接口的方式提供服務(wù)的,所以首先介紹COM的有關(guān)基礎(chǔ)知識(shí)。11.1.1COM基礎(chǔ)SpeechSDK提供了完善的COM接口,所以具備一定的COM編程基礎(chǔ)對(duì)進(jìn)行SpeechSDK編程來(lái)說(shuō)是非常必要的。筆者將簡(jiǎn)要介紹COM編程的基礎(chǔ)知識(shí)。雖然這些知識(shí)對(duì)閱讀本書(shū)來(lái)說(shuō)是足夠了,但是如果你沒(méi)有進(jìn)行過(guò)任何的COM編程實(shí)踐,筆者還是建議你先閱讀一本COM的教科書(shū)。1.什么是COM組件對(duì)象模型(ComponentObjectModel,COM)對(duì)象是符合COM規(guī)范的可重用的軟件組件。符合COM規(guī)范的COM對(duì)象相互之間可以很好地工作,并且可以很容易地集成到應(yīng)用程序中。從應(yīng)用的觀點(diǎn)來(lái)看,一個(gè)COM對(duì)象就是一個(gè)黑箱,應(yīng)用程序可以使用它來(lái)創(chuàng)建一項(xiàng)或多項(xiàng)任務(wù)。COM對(duì)象常常用動(dòng)態(tài)鏈接庫(kù)(DynamicLinkLibraries,DLLs)的形式來(lái)實(shí)現(xiàn)。與傳統(tǒng)的DLL一樣,COM對(duì)象暴露其方法,應(yīng)用程序能調(diào)用這些方法來(lái)實(shí)現(xiàn)對(duì)象所支持的功能。應(yīng)用程序與COM對(duì)象的關(guān)系就像應(yīng)用程序與C++對(duì)象的關(guān)系,但其中也存在一些區(qū)別。1)COM對(duì)象執(zhí)行嚴(yán)格的封裝。你不能簡(jiǎn)單地創(chuàng)建一個(gè)對(duì)象就調(diào)用其中的公用方法。COM對(duì)象的公用方法聚合為一個(gè)或多個(gè)接口。為了使用一個(gè)方法,必須先創(chuàng)建一個(gè)對(duì)象,并從對(duì)象中獲得相應(yīng)的接口。一個(gè)接口一般包含一組方法,通過(guò)它們能使用對(duì)象的特定功能。不能通過(guò)接口來(lái)調(diào)用不屬于該接口的方法。2)創(chuàng)建COM對(duì)象的方法與創(chuàng)建C++對(duì)象的方法不同。有多種方法可以創(chuàng)建COM對(duì)象,但所有的方法都需要使用COM的細(xì)節(jié)技術(shù)。SpeechSDK應(yīng)用程序編程接口(API)包括許多的幫助函數(shù)和方法,它們簡(jiǎn)化了創(chuàng)建大部分Speech對(duì)象的工作。3)必須使用COM的細(xì)節(jié)技術(shù)來(lái)控制對(duì)象的生命期。4)COM對(duì)象不需要明確地裝載。COM對(duì)象一般包含在DLL中。然而,與使用普通DLL中的方法不同,使用COM對(duì)象時(shí),不需要明確地裝載DLL或鏈接靜態(tài)庫(kù)。每一個(gè)COM對(duì)象都具有一個(gè)惟一的注冊(cè)標(biāo)識(shí)。用該標(biāo)識(shí)來(lái)創(chuàng)建對(duì)象時(shí),COM將自動(dòng)地裝載正確的DLL。5)COM是一種二進(jìn)制規(guī)范。COM對(duì)象可用許多種編程語(yǔ)言來(lái)編寫(xiě)和調(diào)用。對(duì)于使用者來(lái)說(shuō),并不需要了解對(duì)象的源程序的任何信息。比如,MicrosoftVisualBasic編寫(xiě)的應(yīng)用程序可以很好地調(diào)用C++編寫(xiě)的COM對(duì)象。下面先介紹COM的幾個(gè)基本概念,包括對(duì)象與接口、GUID、HRESULT類型和指針地址等。(1)對(duì)象與接口理解對(duì)象與接口的區(qū)別是非常重要的。通常用其主要接口的名稱來(lái)稱呼對(duì)象。但是,嚴(yán)格地說(shuō),這兩個(gè)術(shù)語(yǔ)是不能互換使用的。一個(gè)對(duì)象能暴露任何數(shù)量的接口。例如,所有的對(duì)象都必須暴露IUnknown接口,它們一般還暴露至少一個(gè)其他的接口,它們也可能暴露更多的接口。為了使用一個(gè)特定的方法,首先必須獲得正確的接口指針。多個(gè)不同的接口可以暴露同一個(gè)接口。一個(gè)接口就是一組執(zhí)行特定操作的方法。接口定義只是指定了方法的調(diào)用語(yǔ)法和它們的一般功能。任何需要支持一組特定操作的COM對(duì)象都可通過(guò)暴露一個(gè)合適的接口來(lái)實(shí)現(xiàn)這些特定的操作。有些接口非常專業(yè),僅僅由單一的對(duì)象來(lái)暴露。有些接口在許多場(chǎng)合下都非常有用,它們可由許多的對(duì)象來(lái)暴露。最極端的例子是IUnknown接口,所有的COM對(duì)象都需要暴露它。如果一個(gè)對(duì)象暴露一個(gè)接口,它必須支持接口定義的每一個(gè)方法。但是,不同的對(duì)象實(shí)現(xiàn)一個(gè)特定方法的方式是不同的。不同的對(duì)象可能使用不同的算法來(lái)實(shí)現(xiàn)最后的結(jié)果。有時(shí)一個(gè)對(duì)象暴露一組通用的方法,但往往只需要支持其中的一部分方法??墒瞧渌奈幢恢С值姆椒ㄒ残枰鼙怀晒φ{(diào)用,只是它們都只返回E_NOTIMPL。COM標(biāo)準(zhǔn)要求接口一旦發(fā)布,其定義就不能再改變。例如,不能在一個(gè)已有的接口中增加一個(gè)新的方法,而必須創(chuàng)建一個(gè)新的接口。如果沒(méi)有限制接口中必須有什么樣的方法,通常的做法是在其下一代接口中包括原有接口的所有方法和其他的新方法。但是一個(gè)接口有幾代版本的情況并不常見(jiàn)。通常所有的版本實(shí)現(xiàn)完全相同的功能,只是在具體實(shí)現(xiàn)細(xì)節(jié)上不同。一個(gè)對(duì)象經(jīng)常暴露所有版本的接口。這樣做使較老版本的應(yīng)用程序能繼續(xù)使用對(duì)象的較老版本的接口,而較新版本的應(yīng)用程序能使用較新版本接口的強(qiáng)大功能。一般來(lái)說(shuō),同一家族的接口具有相同的名稱,在名稱后加一個(gè)整數(shù)來(lái)表示不同的版本。比如,原來(lái)的接口叫做IMyInterface,接下來(lái)的兩代版本就分別叫做IMyInterface2和IMyInterface3。(2)GUID全球惟一標(biāo)識(shí)符(GloballyUniqueIdentifiers,GUIDs)是COM編程模型的關(guān)鍵部分。從本質(zhì)上來(lái)說(shuō),GUID是一個(gè)128位的結(jié)構(gòu)。然而,GUID在創(chuàng)建時(shí)必須保證不能出現(xiàn)兩個(gè)相同的GUID。COM在如下的兩個(gè)方面廣泛地使用GUID:1)惟一地標(biāo)識(shí)一個(gè)特定的COM對(duì)象。賦予一個(gè)COM對(duì)象的GUID叫做一個(gè)類標(biāo)識(shí)(classID,CLSID)。當(dāng)需要?jiǎng)?chuàng)建一個(gè)相關(guān)的COM對(duì)象的實(shí)例時(shí),需要使用CLSID。2)惟一地標(biāo)識(shí)一個(gè)特定的COM接口。與一個(gè)COM接口相關(guān)的GUID叫做接口標(biāo)識(shí)(interfaceID,IID)。當(dāng)從一個(gè)對(duì)象中請(qǐng)求一個(gè)特定接口時(shí),需要使用IID。不管哪個(gè)對(duì)象暴露接口,該接口的IID都是相同的。為了敘述方便,在文檔中經(jīng)常使用對(duì)象和接口的描述名稱來(lái)引用對(duì)象和接口。雖然這樣做并不會(huì)在文檔中引起混亂,但是,嚴(yán)格地說(shuō),并不能保證不會(huì)有兩個(gè)或多個(gè)不同的對(duì)象或接口具有相同的描述名。惟一的不能引起歧義的方法是用GUID來(lái)引用對(duì)象或接口。雖然GUID是一種結(jié)構(gòu),但卻經(jīng)常表示為對(duì)應(yīng)的字符串。最常用的GUID字符串形式是“{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}”,其中x為一個(gè)十六進(jìn)制整數(shù)。由于實(shí)際的GUID很長(zhǎng),并且很容易寫(xiě)錯(cuò),所以一般還提供一個(gè)等價(jià)的名稱。在調(diào)用CoCreateInstance之類的函數(shù)時(shí),可以使用這個(gè)名稱而不使用實(shí)際的GUID結(jié)構(gòu)。通常的命名慣例是分別在對(duì)象或接口的描述名稱前加上CLSID_或IID_作為前綴。例如,ISpVoice接口的CLSID的名稱是CLSID_ISpVoice。(3)HRESULT類型值所有的COM方法都返回一個(gè)32位的HRESULT類型的值。對(duì)大部分方法而言,HRESULT本質(zhì)上是一個(gè)包含獨(dú)立的兩部分信息的結(jié)構(gòu):1)方法調(diào)用成功了還是失敗了;2)關(guān)于方法所支持操作的結(jié)果的更詳細(xì)信息。有些方法僅僅返回在Winerror.h中定義的標(biāo)準(zhǔn)的HRESULT類型值。然而,方法可以返回自己定義的HRESULT類型值,以提供更專用的信息。當(dāng)然這樣的自定義值一般會(huì)在方法的參考文檔中說(shuō)明。需要注意的是,參考文檔可能不會(huì)列出標(biāo)準(zhǔn)的HRESULT類型值,但方法卻可能返回標(biāo)準(zhǔn)值。雖然HRESULT類型值經(jīng)常用來(lái)返回錯(cuò)誤信息,但不要將它們看成錯(cuò)誤碼。由于說(shuō)明成功或失敗的位在包含詳細(xì)信息的數(shù)據(jù)中是分別存儲(chǔ)的,所以HRESULT類型值可以包含任何數(shù)量的成功和失敗代碼。作為一種慣例,成功碼的名稱具有S_前綴,錯(cuò)誤碼的名稱具有E_前綴。比如,最常用的成功碼和錯(cuò)誤碼分別是S_OK和E_FAIL。由于COM方法可能返回許多不同的成功碼或錯(cuò)誤碼,因此必須很小心地測(cè)試HRESULT類型的值。例如,假設(shè)一個(gè)方法在文檔中說(shuō)明成功,返回S_OK,不成功則返回E_FAIL。這時(shí),請(qǐng)注意,該方法可能還會(huì)返回其他的成功碼或錯(cuò)誤碼。下面的代碼段說(shuō)明了使用簡(jiǎn)單測(cè)試的危險(xiǎn)。//hr是該方法返回的HRESULT類型值if(hr==E_FAIL){//處理錯(cuò)誤}else{//處理成功}只有在該方法只返回E_FAIL來(lái)指示失敗時(shí),上面的測(cè)試才能正常工作。然而,該方法還可能返回一個(gè)E_NOTIMPL或E_INVALIDARG之類的錯(cuò)誤值。上面的代碼將會(huì)將它們解釋為成功,這將可能導(dǎo)致程序的失敗。如果需要關(guān)于方法運(yùn)行結(jié)果的更詳細(xì)的信息,必須測(cè)試每一個(gè)相關(guān)的HRESULT值。但經(jīng)常只關(guān)心方法是成功的還是失敗的。一種可靠的測(cè)試HRESULT類型值說(shuō)明成功還是失敗的方法是利用如下的宏來(lái)判斷,這些宏定義在Winerror.h中。1)宏SUCCEEDED返回TRUE作為成功碼,返回FALSE作為失敗碼;2)宏FAILED返回TRUE作為失敗碼,返回FALSE作為成功碼;可以使用宏FAILED來(lái)修改上面的代碼段://hr是該方法返回的HRESULT類型值

if(FAILED(hr)){//處理錯(cuò)誤}else{//處理成功}這段代碼能合理地處理E_NOTIMPL和E_INVALIDARG之類的失敗碼。大多數(shù)的COM方法返回結(jié)構(gòu)化的HRESULT類型值,只有很少數(shù)量的方法使用HRESULT來(lái)返回簡(jiǎn)單的整數(shù)值,這類方法經(jīng)常是成功的。如果將這類整數(shù)值傳給宏SUCCESS,該宏將總是返回TRUE。常用的例子是IUnknown::Release方法,它減少一次對(duì)象的引用計(jì)數(shù)并返回當(dāng)前的引用計(jì)數(shù)。將在管理對(duì)象的生命期一節(jié)中討論引用計(jì)數(shù)的問(wèn)(4)指針地址閱讀一些COM方法的參考文檔時(shí),經(jīng)??吹饺缦碌恼f(shuō)明:HRESULTCreateDevice(IDirect3DDevice8**ppReturnedDeviceInterface);C或C++開(kāi)發(fā)人員熟悉普通的指針,但是COM經(jīng)常使用另外的間接層。這種間接的第二層用兩個(gè)星號(hào)(**)跟著類型聲明來(lái)表示。變量名一般使用“pp”作為前綴。在上面的例子中,參數(shù)ppReturnedDeviceInterface表示指向IDirect3DDevice8接口的指針的地址。與C++不同,不需要直接訪問(wèn)COM對(duì)象的方法,而必須獲取指向方法的接口的指針。然后像調(diào)用指向C++方法的指針一樣來(lái)調(diào)用方法。例如,使用如下的語(yǔ)法來(lái)調(diào)用方法IMyInterface::DoSomethingmethod:IMyInterface*pMyIface;...pMyIface->DoSomething(...);這樣做的原因是我們并不直接創(chuàng)建接口指針,而是必須調(diào)用不同的方法(例如上述的CreateDevice)來(lái)創(chuàng)建接口指針。為了使用這種方法來(lái)獲取接口指針,應(yīng)聲明一個(gè)指向需要的接口的指針變量,并將該指針變量的地址,即一個(gè)指針的地址,傳遞給該方法。當(dāng)該方法返回時(shí),該變量將指向你要求的接口,可以使用該指針來(lái)調(diào)用接口的任何方法。將在使用COM接口一節(jié)中更詳細(xì)地討論接口指針的問(wèn)題。2.創(chuàng)建COM對(duì)象有多種方法可以創(chuàng)建COM對(duì)象。以下是在編程中最常用的兩種方法。1)直接法:將對(duì)象的CLSID傳給CoCreateInstance函數(shù)。該函數(shù)將創(chuàng)建對(duì)象的一個(gè)實(shí)例,并返回指向你所指定接口的指針。2)間接法:調(diào)用一個(gè)特殊的方法或函數(shù)來(lái)創(chuàng)建對(duì)象。這類方法創(chuàng)建對(duì)象并返回該對(duì)象的接口。使用這種方式來(lái)創(chuàng)建對(duì)象時(shí),通常并不能指定需要返回的接口。創(chuàng)建對(duì)象之前,必須調(diào)用CoInitialize函數(shù)來(lái)初始化COM。如果使用間接法來(lái)創(chuàng)建對(duì)象,對(duì)象的創(chuàng)建方法將自動(dòng)完成COM初始化。如果使用CoCreateInstance來(lái)創(chuàng)建對(duì)象,則必須明確地調(diào)用CoInitialize。當(dāng)完成了所有的COM工作后,必須調(diào)用CoUninitialize來(lái)卸載COM。如果調(diào)用了CoInitialize,則必須對(duì)應(yīng)地調(diào)用一次CoUninitialize。一般地說(shuō),需要明確,初始化COM的應(yīng)用程序在其啟動(dòng)過(guò)程中初始化COM,在其清除過(guò)程中卸載COM。用CoCreateInstance來(lái)創(chuàng)建一個(gè)COM對(duì)象的實(shí)例需要使用該對(duì)象的CLSID。如果其CLSID是公開(kāi)的,可以在其參考手冊(cè)或相應(yīng)的頭文件中找到。如果其CLSID不是公開(kāi)的,則不能使用直接法來(lái)創(chuàng)建該對(duì)象。CoCreateInstance函數(shù)有5個(gè)參數(shù),一般可以按如下方式來(lái)設(shè)置其參數(shù)。1)rclsid:將該參數(shù)設(shè)為需要?jiǎng)?chuàng)建的對(duì)象的CLSID。2)pUnkOuter:將該參數(shù)設(shè)為NULL。只有在聚合對(duì)象時(shí)才需要使用該參數(shù)。參見(jiàn)關(guān)于聚合的討論。3)dwClsContext:將該參數(shù)設(shè)為CLSCTX_INPROC_SERVER。該值說(shuō)明對(duì)象是在DLL中實(shí)現(xiàn)的,將作為你的應(yīng)用程序進(jìn)程的一部分來(lái)運(yùn)行。4)riid:將該參數(shù)設(shè)為需要返回的接口的IID。該函數(shù)將創(chuàng)建指定的對(duì)象,并通過(guò)參數(shù)ppv返回所請(qǐng)求的接口指針。5)ppv:將該參數(shù)設(shè)為riid所指定的接口的指針地址。該變量應(yīng)該聲明為一個(gè)指向請(qǐng)求的接口的指針。在參數(shù)表中,該參數(shù)應(yīng)被強(qiáng)制為(LPVOID*)類型。例如,下面的代碼段創(chuàng)建了ISpVoice對(duì)象的一個(gè)新的實(shí)例,函數(shù)返回時(shí),m_IpVoice變量是指向ISpVoice接口的指針。如果發(fā)生錯(cuò)誤,程序?qū)⒔K止,并顯示一個(gè)消息框。CComPtr<ISpVoice>m_IpVoice=NULL;

HRESULThr; hr=m_IpVoice.CoCreateInstance(CLSID_SpVoice);

if(FAILED(hr)){ AfxMessageBox("Errorcreatingvoice"); returnFALSE;}用間接法創(chuàng)建對(duì)象往往簡(jiǎn)單得多。只要將接口指針的地址傳給對(duì)象的創(chuàng)建方法,該方法就會(huì)創(chuàng)建對(duì)象并返回接口指針。但間接地創(chuàng)建對(duì)象時(shí),一般不能選擇返回哪個(gè)接口。但是可以指定如何來(lái)創(chuàng)建對(duì)象。3.IUnknown接口所有的COM對(duì)象都支持一個(gè)叫做IUnknown的接口。該接口提供了對(duì)對(duì)象的生命期的控制和檢索對(duì)象的其他接口的方法。IUnknown接口有以下3個(gè)方法。1)AddRef:當(dāng)一個(gè)接口或另一個(gè)應(yīng)用程序與對(duì)象綁定時(shí),對(duì)對(duì)象的引用計(jì)數(shù)加1。2)QueryInterface:查詢對(duì)象所支持的功能,并請(qǐng)求指向指定的接口的指針。3)Release:對(duì)對(duì)象的引用計(jì)數(shù)減1。當(dāng)引用計(jì)數(shù)變?yōu)?時(shí),對(duì)象將被釋放。AddRef和Release方法維護(hù)對(duì)象的引用計(jì)數(shù)。例如,當(dāng)創(chuàng)建一個(gè)對(duì)象時(shí),該對(duì)象的引用計(jì)數(shù)變?yōu)?。每次一個(gè)函數(shù)返回一個(gè)指向該對(duì)象的接口的指針時(shí),該函數(shù)都必須調(diào)用AddRef來(lái)增加其引用計(jì)數(shù)。AddRef的每一次調(diào)用都必須與Release的調(diào)用相匹配。在指針被釋放前必須對(duì)該指針調(diào)用Release。當(dāng)一個(gè)對(duì)象的引用計(jì)數(shù)變?yōu)?時(shí),該對(duì)象將被釋放,它的所有接口都將變?yōu)闊o(wú)效接口。QueryInterface方法用來(lái)確定一個(gè)對(duì)象是否支持指定的接口。如果一個(gè)對(duì)象支持一個(gè)接口,QueryInterface返回一個(gè)指向該接口的指針。然后就可以使用該接口的方法來(lái)與對(duì)象進(jìn)行通信。如果QueryInterface成功地返回一個(gè)指向接口的指針,它將明確地調(diào)用AddRef來(lái)增加引用計(jì)數(shù)。因此在釋放該接口的指針之前,應(yīng)用程序必須調(diào)用Release來(lái)減少引用計(jì)數(shù)。4.使用COM接口獲得接口指針后,可以用該指針來(lái)訪問(wèn)接口的任何方法。在許多情形中,從創(chuàng)建方法接收到的接口指針就是所需要的。實(shí)際上,只暴露一個(gè)除IUnknown之外的接口的對(duì)象是很不常見(jiàn)的。相反,許多的對(duì)象暴露多個(gè)接口,需要指向這些接口的多個(gè)指針。如果需要?jiǎng)?chuàng)建方法所返回的接口之外的更多的接口,則并不需要再創(chuàng)建一個(gè)新的對(duì)象??梢允褂脤?duì)象的IUnknown::QueryInterface方法來(lái)請(qǐng)求其他接口的指針。如果使用CoCreateInstance來(lái)創(chuàng)建對(duì)象,則可以請(qǐng)求一個(gè)IUnknown接口指針,然后調(diào)用IUnknown::QueryInterface方法來(lái)請(qǐng)求需要的每一個(gè)接口。然而,當(dāng)只需要一個(gè)接口時(shí),這種方法顯得很不方便。而且,如果使用不允許指定哪個(gè)接口應(yīng)該返回的創(chuàng)建方法時(shí),這種方法更不能工作。在實(shí)踐中,經(jīng)常不需要獲得一個(gè)明確的IUnknown指針,因?yàn)樗械腃OM接口都是從IUnknown接口繼承或擴(kuò)展而來(lái)的。擴(kuò)展一個(gè)接口很像繼承一個(gè)C++類。子接口暴露父接口的所有方法,并增加一個(gè)或多個(gè)自己的方法。事實(shí)上,經(jīng)??匆?jiàn)“繼承”而不是“擴(kuò)展”。需要記住的是,繼承只能出現(xiàn)在對(duì)象的內(nèi)部。應(yīng)用程序不能繼承或擴(kuò)展另一個(gè)對(duì)象的接口,但是可以使用子接口來(lái)調(diào)用它自己或其父接口的方法。由于所有接口都是IUnknown的子接口,因此可以使用已經(jīng)獲得的任何該對(duì)象接口的指針來(lái)調(diào)用QueryInterface。這樣做時(shí),需要提供你請(qǐng)求的接口的IID和當(dāng)方法返回時(shí)存放接口指針的指針。5.管理COM對(duì)象的生命期當(dāng)對(duì)象被創(chuàng)建時(shí),系統(tǒng)將分配必需的內(nèi)存資源。當(dāng)一個(gè)對(duì)象不再需要時(shí),應(yīng)該刪除它。系統(tǒng)將收回它所占有的內(nèi)存,以用于其他目的。對(duì)于C++對(duì)象,應(yīng)直接使用new和delete操作符來(lái)控制對(duì)象的生命期。COM不允許直接創(chuàng)建或刪除對(duì)象。其原因是同一對(duì)象可能被多個(gè)應(yīng)用程序所使用。如果其中的一個(gè)應(yīng)用程序要?jiǎng)h除該對(duì)象,其他的應(yīng)用程序就可能失敗。實(shí)際上,COM采用引用計(jì)數(shù)系統(tǒng)來(lái)控制對(duì)象的生命期。對(duì)象的引用計(jì)數(shù)就是其中的接口被請(qǐng)求的次數(shù)。接口每被請(qǐng)求一次,其引用計(jì)數(shù)都將增加。當(dāng)不再需要接口時(shí),應(yīng)用程序?qū)⑨尫旁摻涌?,并減少其引用計(jì)數(shù)。只要引用計(jì)數(shù)大于0,對(duì)象將保留在內(nèi)存中。當(dāng)引用計(jì)數(shù)變?yōu)?時(shí),對(duì)象將釋放自己。我們不必關(guān)心對(duì)象的引用計(jì)數(shù),只需要正確地獲取和釋放對(duì)象的接口,對(duì)象將具有適當(dāng)?shù)纳?。合理地處理引用?jì)數(shù)對(duì)COM編程來(lái)說(shuō)是非常重要的。處理不當(dāng)將導(dǎo)致內(nèi)存泄漏。COM編程人員最常見(jiàn)的錯(cuò)誤是不釋放接口。當(dāng)出現(xiàn)這樣的錯(cuò)誤時(shí),引用計(jì)數(shù)將永遠(yuǎn)不能變?yōu)?,對(duì)象將不確定地保留在內(nèi)存中。只要獲得一個(gè)新的接口指針,引用計(jì)數(shù)就必須調(diào)用IUnknown::AddRef來(lái)增加。但是,應(yīng)用程序通常不需要調(diào)用該方法。如果通過(guò)調(diào)用一個(gè)對(duì)象創(chuàng)建方法或IUnknown::QueryInterface來(lái)獲得接口指針,對(duì)象將自動(dòng)地增加引用計(jì)數(shù)。如果用其他的方法來(lái)創(chuàng)建接口指針,比如拷貝已有的指針,就必須明確地調(diào)用IUnknown::AddRef。否則,當(dāng)釋放原來(lái)的接口指針時(shí),你得到的對(duì)象可能被破壞,即使你還需要使用該指針的拷貝。不論明確地或?qū)ο笞詣?dòng)地增加了引用計(jì)數(shù),都必須釋放接口指針。當(dāng)不再需要接口指針時(shí),調(diào)用IUnknown::Release來(lái)減少引用計(jì)數(shù)。一種常用的方法是,將所有的接口指針初始化為NULL,并在釋放接口后將它們重新設(shè)為NULL。這樣可以在清除代碼中測(cè)試所有的接口指針。那些不為NULL的接口指針就是仍然活動(dòng)的,需要在退出應(yīng)用程序之前釋放它們。6.用C來(lái)操作COM對(duì)象雖然C++是最常用的COM編程語(yǔ)言,有些時(shí)候也需要使用C語(yǔ)言來(lái)訪問(wèn)COM對(duì)象。這樣做更加直截了當(dāng),但也要用到更加復(fù)雜的語(yǔ)法。所有的方法都需要一個(gè)額外的參數(shù),放在參數(shù)表的最前面。這個(gè)參數(shù)必須設(shè)為接口指針。而且,必須明確地引用對(duì)象的vtable。每個(gè)COM對(duì)象都有一個(gè)包含指向?qū)ο笏┞兜姆椒ǖ闹羔樀牧斜怼=涌谥羔樦赶騰table的相應(yīng)位置,該位置包含了指向你要調(diào)用的特定方法的指針。使用C++時(shí),不必關(guān)心vtable,因?yàn)樗贑++中是不可見(jiàn)的。當(dāng)然,如果需要使用C來(lái)調(diào)用COM方法,就必須包括一個(gè)明確引用vtable的間接的額外層。有些組件在它們的頭文件中定義了一些宏,它們能很好地解決如何正確地調(diào)用COM。7.用ATL來(lái)處理COM接口如果使用活動(dòng)模板庫(kù)(ActiveTemplateLibrary,ATL)來(lái)處理MicrosoftSpeech,為了兼容ATL,必須首先重新聲明接口。這將允許你合理地使用CComQIPtr類來(lái)獲取指向接口的指針。如果沒(méi)有為ATL重新聲明接口,則會(huì)得到下面的錯(cuò)誤信息:C:\ProgramFiles\MicrosoftVisualStudio\VC98\ATL\INCLUDE\atlbase.h(566):errorC2787:'ISpVoice':noGUIDhasbeenassociatedwiththisobject11.1.2SAPI接口SAPI提供應(yīng)用程序和語(yǔ)音引擎之間的高層接口,它實(shí)現(xiàn)并隱藏了控制和管理不同語(yǔ)音引擎的實(shí)時(shí)操作的底層技術(shù)細(xì)節(jié)。SAPI的結(jié)構(gòu)如圖11-1所示。最基本的語(yǔ)音引擎包括Text-To-Speech(TTS)和語(yǔ)音識(shí)別器。TTS通過(guò)合成聲音來(lái)朗讀文本字符串和文本文件。語(yǔ)音識(shí)別器將人類的語(yǔ)音轉(zhuǎn)換成可閱讀的字符串和文件。1.Text-To-SpeechAPI應(yīng)用程序通過(guò)ISpVoice組件對(duì)象接口(ComponentObjectModelInterface)來(lái)控制Text-To-Speech(TTS)。一旦應(yīng)用程序創(chuàng)建了IspVoice對(duì)象,調(diào)用接口IspVoice的方法ISpVoice::Speak,就能產(chǎn)生朗讀指定的文字的聲音。IspVoice接口還提供了其他一系列的方法來(lái)改變聲音和其合成特征,比如控制語(yǔ)速的ISpVoice::SetRate,控制輸出音量的ISpVoice::SetVolume和改變當(dāng)前語(yǔ)音的ISpVoice::SetVoice。在輸入用于朗讀的文字中還可插入一系列的特殊SAPI控制符,用來(lái)控制輸出聲音的實(shí)時(shí)合成特性,如語(yǔ)音、語(yǔ)調(diào)、重音、語(yǔ)速和音量等。合成標(biāo)志文件sapi.xsd用來(lái)說(shuō)明聲音的合成特性。sapi.xsd是一種標(biāo)準(zhǔn)XML格式文件,它與特定的引擎或當(dāng)前正在使用的語(yǔ)音無(wú)關(guān),是一種簡(jiǎn)單而功能強(qiáng)大的定制TTS語(yǔ)音的方法。IspVoice::Speak方法既能同步地(在語(yǔ)音播放完之后才返回)也能異步地(語(yǔ)音開(kāi)始播放就返回,語(yǔ)音播放在后臺(tái)處理)操作語(yǔ)音。指定SPF_ASYNC作為播放方式時(shí),語(yǔ)音異步播放。這時(shí)可調(diào)用ISpVoice::GetStatus方法來(lái)獲取實(shí)時(shí)狀態(tài)信息,如播放狀態(tài)、當(dāng)前播放的文字位置等。同時(shí),既可以打斷當(dāng)前的播放而立即播放新的文字(需指定SPF_PURGEBEFORESPEAK參數(shù)),也可以在播放完當(dāng)前文字之后再自動(dòng)播放新的文字。除了IspVoice接口以外,SAPI還提供了許多有用的COM接口來(lái)實(shí)現(xiàn)高級(jí)的TTS應(yīng)用程序。(1)事件(Events)SAPI通過(guò)使用標(biāo)準(zhǔn)的回調(diào)機(jī)制(窗口消息、回調(diào)函數(shù)或Win32事件)來(lái)與應(yīng)用程序傳送事件。對(duì)于TTS,事件主要用來(lái)同步語(yǔ)音輸出。應(yīng)用程序能同步處理語(yǔ)音輸出和實(shí)時(shí)動(dòng)作,比如詞語(yǔ)分界、音位或嘴形動(dòng)畫(huà)分界及應(yīng)用程序定制的分界等。應(yīng)用程序可通過(guò)調(diào)用IspNotifySource,IspNotifySink,IspNotifyTranslator,IspEventSink,IspEventSource和ISpNotifyCallbackcan來(lái)初始化和處理這些實(shí)時(shí)事件。(2)詞典(Lexicons)通過(guò)調(diào)用IspContainerLexicon,IspLexicon和IspPhoneConverter接口提供的方法,應(yīng)用程序能為語(yǔ)音合成引擎設(shè)置定制的詞匯發(fā)音。(3)資源(Resources)下面的COM接口用于處理SAPI語(yǔ)音數(shù)據(jù)(比如聲音文件和發(fā)音詞典):IspDataKey,IspRegDataKey,IspObjectTokenInit,IspObjectTokenCategory,IspObjectToken,IenumSpObjectTokens,IspObjectWithToken,IspResourceManager和IspTask。(4)聲音(Audio)SAPI還提供了定制聲音輸出到特定目標(biāo)(如電話和客戶硬件)的接口,包括IspAudio,ISpMMSysAudio,IspStream,IspStreamFormat和IspStreamFormatConverter。2.語(yǔ)音識(shí)別API正如IspVoice是主要的語(yǔ)音合成接口一樣,IspRecoContext是語(yǔ)音識(shí)別的主要接口。與IspVoice一樣,它也是一種ISpEventSource接口,提供了為請(qǐng)求的語(yǔ)音識(shí)別事件接收通知消息的基本載體。有兩種不同的語(yǔ)音識(shí)別引擎(ISpRecognizer),即共享語(yǔ)音識(shí)別引擎(sharedspeechrecognitionengine)和進(jìn)程內(nèi)語(yǔ)音識(shí)別引擎(InProcspeechrecognitionengine)。應(yīng)用程序可以選擇其中的一種。一般推薦使用共享語(yǔ)音識(shí)別引擎,這種引擎能被多個(gè)應(yīng)用程序共享。創(chuàng)建共享IspRecognizer的IspRecoContext接口很簡(jiǎn)單,應(yīng)用程序只需指定參數(shù)為組件的CLSID_SpSharedRecoContext并調(diào)用COM的CoCreateInstance函數(shù)即可。這時(shí),SAPI將設(shè)置音頻輸入流為SAPI的默認(rèn)音頻輸入流。對(duì)于單獨(dú)運(yùn)行于一個(gè)系統(tǒng)中的大型服務(wù)器應(yīng)用程序,其運(yùn)行效率是很重要的。這時(shí)使用進(jìn)程內(nèi)語(yǔ)音識(shí)別引擎更合適。使用進(jìn)程內(nèi)語(yǔ)音識(shí)別引擎有3個(gè)步驟:首先,應(yīng)用程序需指定參數(shù)為組件的CLSID_SpInprocRecoInstance并調(diào)用COM的CoCreateInstance函數(shù)來(lái)創(chuàng)建其自己的進(jìn)程內(nèi)語(yǔ)音識(shí)別IspRecognizer;其次,應(yīng)用程序需調(diào)用ISpRecognizer::SetInput方法(參見(jiàn)IspObjectToken接口的說(shuō)明)來(lái)設(shè)置音頻輸入流;最后,應(yīng)用程序可調(diào)用ISpRecognizer::CreateRecoContext來(lái)獲取IspRecoContext接口。下一步需要為應(yīng)用程序感興趣的事件設(shè)置通知消息。IspRecognizer也是一種IspEventSource接口,自然是一種IspNotifySource接口,因此,應(yīng)用程序能夠從其IspRecoContext接口中調(diào)用IspNotifySource的方法來(lái)指定IspRecoContext所需的消息應(yīng)通知到何處。調(diào)用ISpEventSource::SetInterest方法可以設(shè)定什么樣的事件需要被通知。最重要的事件是SPEI_RECOGNITION,它標(biāo)識(shí)了IspRecognizer已從IspRecoContext中識(shí)別了一些語(yǔ)音。SpeechSDK文檔中SPEVENTENUM的說(shuō)明提供了其他語(yǔ)音識(shí)別事件的詳細(xì)說(shuō)明。最后需要說(shuō)明的是,應(yīng)用程序必須創(chuàng)建、裝載并激活一個(gè)IspRecoGrammar接口。該接口從本質(zhì)上說(shuō)明了什么語(yǔ)音類型,即口述或命令和控制語(yǔ)法。應(yīng)用程序首先應(yīng)調(diào)用ISpRecoContext::CreateGrammar方法創(chuàng)建一個(gè)IspRecoGrammar接口。然后裝載合適的語(yǔ)法,調(diào)用ISpRecoGrammar::LoadDictation方法可裝載口述語(yǔ)法,調(diào)用ISpRecoGrammar::LoadCmdxxx方法可裝載命令和控制語(yǔ)法。最后,為了激活語(yǔ)法并啟動(dòng)識(shí)別,應(yīng)用程序應(yīng)該調(diào)用ISpRecoGrammar::SetDictationState方法設(shè)置口述狀態(tài),或者調(diào)用ISpRecoGrammar::SetRuleState方法或ISpRecoGrammar::SetRuleIdState方法設(shè)置命令和控制狀態(tài)。當(dāng)應(yīng)用程序通過(guò)請(qǐng)求的通知機(jī)制得到通知消息時(shí),SPEVENT結(jié)構(gòu)的lParam成員包含了一個(gè)IspRecoResult接口,應(yīng)用程序能從中確定用IspRecoContext中的哪個(gè)IspRecoGrammar接口已識(shí)別了什么語(yǔ)音。無(wú)論共享的還是進(jìn)程內(nèi)的IspRecognizer接口都能擁有多個(gè)與其關(guān)聯(lián)的IspRecoContexts接口,并且每一個(gè)接口都能通過(guò)自己的事件通知方式得到相應(yīng)的消息??梢詮囊粋€(gè)IspRecoContext接口中創(chuàng)建多個(gè)IspRecoGrammars接口,不同的接口可用于識(shí)別不同的語(yǔ)音類型。11.1.3安裝SpeechSDK進(jìn)行Text-To-Speech編程之前,必須先下載MiscrosoftSpeechSDK,并將它安裝到你的系統(tǒng)中。MiscrosoftSpeechSDK的下載網(wǎng)址是/speech。至筆者編寫(xiě)本章時(shí)為止,最新的SpeechSDK版本是5.1版。下載的speechsdk51.exe是一個(gè)可執(zhí)行的文件包壓縮文件。運(yùn)行它,將安裝文件釋放到一個(gè)臨時(shí)目錄中,執(zhí)行其中的MicrosoftSpeechSDK5.1.msi,將SpeechSDK安裝到相應(yīng)的目錄中。一般選用默認(rèn)的安裝目錄(C:\ProgramFiles\MicrosoftSpeechSDK5.1)。SpeechSDK支持的默認(rèn)語(yǔ)言是英語(yǔ),即安裝SpeechSDK后,系統(tǒng)還只能支持英語(yǔ)的語(yǔ)音。要使系統(tǒng)支持中文和日文語(yǔ)音,還需要下載安裝相應(yīng)的語(yǔ)言包。從相同的網(wǎng)址中下載語(yǔ)言包speechsdk51LangPack.exe。運(yùn)行它,將安裝文件釋放到一個(gè)臨時(shí)目錄中,執(zhí)行其中的MicrosoftSpeechSDK5.1LanguagePack.msi,將中、日文支持安裝到系統(tǒng)中。安裝好SpeechSDK后,語(yǔ)音控制程序?qū)⒈惶砑拥较到y(tǒng)的控制面板中。利用該控制程序可以設(shè)置語(yǔ)音識(shí)別和文字-語(yǔ)音轉(zhuǎn)換的各項(xiàng)屬性,包括語(yǔ)言/語(yǔ)音、語(yǔ)速和輸入設(shè)備等,如圖11-2所示。至此已做好了編寫(xiě)語(yǔ)音程序的準(zhǔn)備工作,可以開(kāi)始編寫(xiě)語(yǔ)音程序了。下面首先介紹文本-語(yǔ)音轉(zhuǎn)換的編程技術(shù)。11.2.1

構(gòu)造CText2Speech類為了便于使用SpeechSDK提供的文本-語(yǔ)音轉(zhuǎn)換COM接口,筆者編寫(xiě)了一個(gè)類CText2Speech,其中封裝了文本-語(yǔ)音轉(zhuǎn)換COM接口的基本方法。借助該類來(lái)編寫(xiě)文本-語(yǔ)音轉(zhuǎn)換程序非常方便。先來(lái)討論該CText2Speech類的設(shè)計(jì),其定義文件列舉如下://///////////////////////////////////////////////////////////////activespeechengine//#include<atlbase.h>externCComModule_Module;#include<atlcom.h>#include"sapi.h"#include<sphelper.h>

/////////////////////////////////////////////////////////////////speechmessage//#defineWM_TTSEVENT

WM_USER+101

/////////////////////////////////////////////////////////////////text-to-speechclass//classCText2Speech

{public:

CText2Speech();

virtual~CText2Speech();

//initialize

BOOLInitialize(HWNDhWnd=NULL);

voidDestroy();

//speak

HRESULTSpeak(constWCHAR*pwcs,DWORDdwFlags=SPF_DEFAULT);

HRESULTPause();

HRESULTResume();

//rate

HRESULTSetRate(longlRateAdjust);

HRESULTGetRate(long*plRateAdjust);

//volume

HRESULTSetVolume(USHORTusVolume);

HRESULTGetVolume(USHORT*pusVolume);

//voice

ULONG

GetVoiceCount();

HRESULTGetVoice(WCHAR**ppszDescription,ULONGlIndex=-1);

HRESULTSetVoice(WCHAR**ppszDescription);

//errorstring

CStringGetErrorString()

{

returnm_sError;

}

//interface

CComPtr<ISpVoice>m_IpVoice;

private:

CStringm_sError;};文件的開(kāi)始幾行語(yǔ)句:#include<atlbase.h>externCComModule_Module;#include<atlcom.h>#include"sapi.h"#include<sphelper.h>

用于使我們的代碼能操作SpeechSDK中的相關(guān)的接口、函數(shù)和常量。SpeechSDK支持事件。為了與窗口交互,這里在類中定義了消息WM_TTSEVENT。當(dāng)發(fā)生Speech事件時(shí),向相應(yīng)的窗口發(fā)送WM_TTSEVENT消息。在窗口中響應(yīng)該消息就響應(yīng)了相應(yīng)的事件。CText2Speech類中定義了一個(gè)操作Text-To-Speech引擎的接口指針m_IpVoice,作為數(shù)據(jù)成員,其定義如下:CComPtr<ISpVoice>m_IpVoice;幾乎所有的Text-To-Speech操作都是借助該指針來(lái)調(diào)用IspVoice接口的方法而實(shí)現(xiàn)的。CText2Speech類實(shí)現(xiàn)了如下的方法://初始化和釋放函數(shù)BOOLInitialize(HWNDhWnd=NULL);voidDestroy();

//語(yǔ)音操作函數(shù)HRESULTSpeak(constWCHAR*pwcs,DWORDdwFlags=SPF_DEFAULT);HRESULTPause();HRESULTResume();

//語(yǔ)速函數(shù)HRESULTSetRate(longlRateAdjust);HRESULTGetRate(long*plRateAdjust);

//音量函數(shù)HRESULTSetVolume(USHORTusVolume);HRESULTGetVolume(USHORT*pusVolume);

//語(yǔ)言函數(shù)ULONG

GetVoiceCount();HRESULTGetVoice(WCHAR**ppszDescription,ULONGlIndex=-1);HRESULTSetVoice(WCHAR**ppszDesc);

//獲取錯(cuò)誤信息函數(shù)CStringGetErrorString()CText2Speech類的構(gòu)造函數(shù)用于初始化Text-To-Speech引擎接口指針m_IpVoice和錯(cuò)誤字符串;析構(gòu)函數(shù)則調(diào)用釋放引擎的Destroy()函數(shù)釋放語(yǔ)音引擎,其代碼如下:CText2Speech::CText2Speech(){

m_IpVoice=NULL;

m_sError=_T("");}CText2Speech::~CText2Speech(){

Destroy();}初始化函數(shù)Initialize首先初始化COM庫(kù),并調(diào)用CoCreateInstance方法初始化語(yǔ)音引擎。然后設(shè)置必須響應(yīng)的引擎事件,并指定響應(yīng)事件消息的窗口句柄。該窗口句柄是作為函數(shù)的參數(shù)傳入的。Initialize函數(shù)的代碼如下:BOOLCText2Speech::Initialize(HWNDhWnd){

if(FAILED(CoInitialize(NULL)))

{

m_sError=_T("ErrorintializationCOM");

returnFALSE;

}

HRESULThr;

hr=m_IpVoice.CoCreateInstance(CLSID_SpVoice);

if(FAILED(hr))

{

m_sError=_T("Errorcreatingvoice");

returnFALSE;

}

hr=m_IpVoice->SetInterest(SPFEI(SPEI_VISEME),SPFEI(SPEI_VISEME));

if(FAILED(hr))

{

m_sError=_T("Errorcreatinginterest...seriously");

returnFALSE;

}

if(::IsWindow(hWnd))

{

hr=m_IpVoice->SetNotifyWindowMessage(hWnd,WM_TTSEVENT,0,0);

if(FAILED(hr))

{

m_sError=_T("Errorsettingnotificationwindow");

returnFALSE;

}

}

returnTRUE;}釋放函數(shù)則釋放語(yǔ)音引擎接口和COM庫(kù),其代碼如下:voidCText2Speech::Destroy(){

if(m_IpVoice)

m_IpVoice.Release();

CoUninitialize();}語(yǔ)音、語(yǔ)速、音量函數(shù)都是通過(guò)m_IpVoice成員直接調(diào)用ISpVoice接口的相關(guān)方法來(lái)實(shí)現(xiàn)的:HRESULTCText2Speech::Speak(constWCHAR*pwcs,DWORDdwFlags){returnm_IpVoice->Speak(pwcs,dwFlags,NULL);}

HRESULTCText2Speech::Pause(){returnm_IpVoice->Pause();}

HRESULTCText2Speech::Resume(){

returnm_IpVoice->Resume();}

//rateHRESULTCText2Speech::SetRate(longlRateAdjust){returnm_IpVoice->SetRate(lRateAdjust);}

HRESULTCText2Speech::GetRate(long*plRateAdjust){

returnm_IpVoice->GetRate(plRateAdjust);}

//volumeHRESULTCText2Speech::SetVolume(USHORTusVolume){

returnm_IpVoice->SetVolume(usVolume);}

HRESULTCText2Speech::GetVolume(USHORT*pusVolume){

returnm_IpVoice->GetVolume(pusVolume);}

語(yǔ)言函數(shù)的實(shí)現(xiàn)比較復(fù)雜。由于IspVoice接口提供的語(yǔ)言函數(shù),都只與抽象的語(yǔ)音語(yǔ)言接口ISpObjectToken相關(guān),而我們能看到的卻是語(yǔ)音語(yǔ)言的描述,比如,通過(guò)控制面板的語(yǔ)音程序所能見(jiàn)到的就是語(yǔ)音語(yǔ)言的描述。因此,筆者設(shè)計(jì)了直接對(duì)語(yǔ)音語(yǔ)言進(jìn)行操作的語(yǔ)言函數(shù),包括獲取系統(tǒng)中已安裝的語(yǔ)音語(yǔ)言數(shù)目,設(shè)置指定的語(yǔ)音語(yǔ)言,獲取指定的語(yǔ)音語(yǔ)言描述(包括當(dāng)前設(shè)定的語(yǔ)音語(yǔ)言)。它們的代碼如下:ULONGCText2Speech::GetVoiceCount(){

HRESULT

hr=S_OK;

CComPtr<ISpObjectToken>

cpVoiceToken;

CComPtr<IEnumSpObjectTokens>

cpEnum;

ULONG

ulCount=-1;

//Enumeratetheavailablevoices

hr=SpEnumTokens(SPCAT_VOICES,NULL,NULL,&cpEnum);

if(FAILED(hr))

{

m_sError=_T("Errortoenumeratevoices");

return-1;

}

//Getthenumberofvoices

hr=cpEnum->GetCount(&ulCount);

if(FAILED(hr))

{

m_sError=_T("Errortogetvoicecount");

return-1;

}

returnulCount;}

HRESULTCText2Speech::GetVoice(WCHAR**ppszDescription,ULONGlIndex){

HRESULT

hr=S_OK;

CComPtr<ISpObjectToken>

cpVoiceToken;

CComPtr<IEnumSpObjectTokens>

cpEnum;

ULONG

ulCount=0;

if(lIndex==-1)

{

//currentvoice

//

hr=m_IpVoice->GetVoice(&cpVoiceToken);

if(FAILED(hr))

{

m_sError=_T("Errortogetcurrentvoice");

returnhr;

}

SpGetDescription(cpVoiceToken,ppszDescription);

if(FAILED(hr))

{

m_sError=_T("Errortogetcurrentvoicedescription");

returnhr;

}

}

else

{

//elseothervoices,weshouldenumeratethevoicelistfirst

//Enumeratetheavailablevoices

hr=SpEnumTokens(SPCAT_VOICES,NULL,NULL,&cpEnum);

if(FAILED(hr))

{

m_sError=_T("Errortoenumeratevoices");

returnhr;

}

//Getthenumberofvoices

hr=cpEnum->GetCount(&ulCount);

if(FAILED(hr))

{

m_sError=_T("Errortovoicecount");

returnhr;

}

//rangecontrol

ASSERT(lIndex>=0);

ASSERT(lIndex<ulCount);

//Obtainspecifiedvoiceid

ULONGl=0;

while(SUCCEEDED(hr))

{

cpVoiceToken.Release();

hr=cpEnum->Next(1,&cpVoiceToken,NULL);

if(FAILED(hr))

{

m_sError=_T("Errortogetvoicetoken");

returnhr;

}

if(l==lIndex)

{

hr=SpGetDescription(cpVoiceToken,ppszDescription);

if(FAILED(hr))

{

m_sError=_T("Errortogetvoicedescription");

returnhr;

}

break;

}

l++;

}

}

returnhr;}HRESULTCText2Speech::SetVoice(WCHAR**ppszDescription){

HRESULT

hr=S_OK;

CComPtr<ISpObjectToken>

cpVoiceToken;

CComPtr<IEnumSpObjectTokens>

cpEnum;

ULONG

ulCount=0;

//Enumeratetheavailablevoices

hr=SpEnumTokens(SPCAT_VOICES,NULL,NULL,&cpEnum);

if(FAILED(hr))

{

m_sError=_T("Errortoenumeratevoices");

returnhr;

}

//Getthenumberofvoices

hr=cpEnum->GetCount(&ulCount);

if(FAILED(hr))

{

m_sError=_T("Errortovoicecount");

returnhr;

}

//Obtainspecifiedvoiceid

while(SUCCEEDED(hr)&&ulCount--)

{

cpVoiceToken.Release();

hr=cpEnum->Next(1,&cpVoiceToken,NULL);

if(FAILED(hr))

{

m_sError=_T("Errortovoicetoken");

returnhr;

}

WCHAR*pszDescription1;

hr=SpGetDescription(cpVoiceToken,&pszDescription1);

if(FAILED(hr))

{

m_sError=_T("Errortogetvoicedescription");

returnhr;

}

if(!wcsicmp(pszDescription1,*ppszDescription))

{

hr=m_IpVoice->SetVoice(cpVoiceToken);

if(FAILED(hr))

{

m_sError=_T("Errortosetvoice");

returnhr;

}

break;

}

}

returnhr;}如果不想處理有關(guān)語(yǔ)言的細(xì)節(jié)問(wèn)題,只想顯示和選擇系統(tǒng)可提供的語(yǔ)音引擎,則可以直接調(diào)用SpeechSDK提供的兩個(gè)幫助函數(shù)SpInitTokenComboBox和SpInitTokenListBox來(lái)實(shí)現(xiàn)語(yǔ)音語(yǔ)言的顯示和選擇,其代碼如下:HRESULTSpInitTokenComboBox( HWND hwnd,constWCHAR*pszCatName,constWCHAR*pszRequiredAttrib=NULL,constWCHAR*pszOptionalAttrib=NULL); HRESULTSpInitTokenListBox(HWND hwnd,constWCHAR*pszCatName,constWCHAR*pszRequiredAttrib=NULL,constWCHAR*pszOptionalAttrib=NULL);CText2Speech類具有很好的錯(cuò)誤處理機(jī)制。一旦調(diào)用某個(gè)函數(shù)發(fā)生了錯(cuò)誤,響應(yīng)的錯(cuò)誤信息都將存放在m_sError數(shù)據(jù)成員中。可通過(guò)GetErrorString函數(shù)來(lái)獲得錯(cuò)誤描述。11.2.2示例:用CText2Speech類編制文字朗讀程序下面使用CText2Speech類來(lái)編寫(xiě)一個(gè)文字朗讀程序Reciter,其界面如圖11-3所示。用VisualC++編制Reciter的步驟和要點(diǎn)如下:1)使用AppWizard生成一個(gè)基于對(duì)話框的項(xiàng)目Reciter。2)將Text2Speech.H,Text2Speech.CPP增加到Reciter項(xiàng)目中。3)在資源編輯器中編輯好響應(yīng)的控件。4)用ClassWizard為控件在CReciterDlg類中生成相應(yīng)的成員。5)修改ReciterDlg.h文件,為類CReciterDlg增加相應(yīng)的變量和函數(shù)。6)用ClassWizard為CReciterDlg類添加對(duì)控件和消息的響應(yīng)函數(shù)。ReciterDlg.h的代碼如下所示:#include"Text2Speech.h"

//CONTANTSOFMOUTH#defineCHARACTER_WIDTH 128#defineCHARACTER_HEIGHT 128#defineWEYESNAR 14//eyepositions#defineWEYESCLO 15

///////////////////////////////////////////////////////////////////////////MouthMappingArray(fromMicrosoft'sTTSAppExample)//constintg_iMapVisemeToImage[22]={0, //SP_VISEME_0=0, //Silence11, //SP_VISEME_1, //AE,AX,AH11, //SP_VISEME_2, //AA11, //SP_VISEME_3, //AO10, //SP_VISEME_4, //EY,EH,UH11, //SP_VISEME_5, //ER9, //SP_VISEME_6, //y,IY,IH,IX2, //SP_VISEME_7, //w,UW13, //SP_VISEME_8, //OW9, //SP_VISEME_9, //AW12, //SP_VISEME_10, //OY11, //SP_VISEME_11, //AY9, //SP_VISEME_12, //h3, //SP_VISEME_13, //r6, //SP_VISEME_14, //l7, //SP_VISEME_15, //s,z8, //SP_VISEME_16, //SH,CH,JH,ZH5, //SP_VISEME_17, //TH,DH4, //SP_VISEME_18, //f,v7, //SP_VISEME_19, //d,t,n9, //SP_VISEME_20, //k,g,NG1 //SP_VISEME_21, //p,b,m};

///////////////////////////////////////////////////////////////////////////CReciterDlgdialog

classCReciterDlg:publicCDialog{//Constructionpublic: CReciterDlg(CWnd*pParent=NULL); //standardconstructor

//DialogData //{{AFX_DATA(CReciterDlg) enum{IDD=IDD_RECITER_DIALOG}; CStatic m_cMouth; CListBox m_ListVoices; CString m_strText; //}}AFX_DATA

//ClassWizardgeneratedvirtualfunctionoverrides //{{AFX_VIRTUAL(CReciterDlg) protected: virtualvoidDoDataExchange(CDataExchange*pDX); //DDX/DDVsupport //}}AFX_VIRTUAL

CText2Speechm_Text2Speech;

voidInitText2Speech(); voidInitMouthImageList();

private: CImageList m_cMouthList; Int m_iMouthBmp;CRectm_cMouthRect;//Implementationprotected: HICONm_hIcon;

//Generatedmessagemapfunctions //{{AFX_MSG(CReciterDlg) virtualBOOLOnInitDialog(); afx_msgvoidOnSysCommand(UINTnID,LPARAMlParam); afx_msgvoidOnPaint(); afx_msgHCURSOROnQueryDragIcon(); afx_msgvoidOnButtonSpeak(); afx_msgvoidOnSelchangeList1(); afx_msgvoidOnButtonStop(); afx_msgvoidOnButtonResume(); //}}AFX_MSG afx_msgLRESULTOnMouthEvent(WPARAM,LPARAM); DECLARE_MESSAGE_MAP()};注意,在CReciterDlg類中定義了一個(gè)CText2Speech類的對(duì)象。7)在ReciterDlg.cpp中編寫(xiě)各成員函數(shù)的代碼。成員函數(shù)InitText2Speech用于初始化語(yǔ)音引擎并找出系統(tǒng)中的所有語(yǔ)音語(yǔ)言,顯示在語(yǔ)音列表中,其代碼如下所示:voidCReciterDlg::InitText2Speech(){ if(!m_Text2Speech.Initialize(m_hWnd)) AfxMessageBox(m_Text2Speech.GetErrorString());

longlCount=m_Text2Speech.GetVoiceCount(); WCHAR*pszID; for(longl=0;l<lCount;++l) { m_Text2Speech.GetVoice(&pszID,l); m_ListVoices.AddString(CString(pszID)); } m_Text2Speech.GetVoice(&pszID,-1); m_ListVoices.SelectString(0,CString(pszID));}成員函數(shù)InitMouthImageList用于初始化朗讀者圖像列表,其代碼如下:voidCReciterDlg::InitMouthImageList(){ m_cMouth.GetClientRect(&m_cMouthRect); m_cMouth.ClientToScreen(&m_cMouthRect); ScreenToClient(&m_cMouthRect); m_cMouth.ShowWindow(SW_HIDE);

CBitmapbmp; m_cMouthList.Create(CHARACTER_WIDTH,CHARACTER_HEIGHT,ILC_COLOR32|ILC_MASK,1,0);

bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICFULL)); m_cMouthList.Add(&bmp,RGB(255,0,255)); bmp.Detach();

bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH2)); m_cMouthList.Add(&bmp,RGB(255,0,255)); bmp.Detach();

bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH3)); m_cMouthList.Add(&bmp,RGB(255,0,255)); bmp.Detach();

bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH4)); m_cMouthList.Add(&bmp,RGB(255,0,255)); bmp.Detach();

bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH5)); m_cMouthList.Add(&bmp,RGB(255,0,255)); bmp.Detach();

bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH6)); m_cMouthList.Add(&bmp,RGB(255,0,255)); bmp.Detach();

bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH7)); m_cMouthList.Add(&bmp,RGB(255,0,255)); bmp.Detach();

bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH8)); m_cMouthList.Add(&bmp,RGB(255,0,255)); bmp.Detach();

bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH9)); m_cMouthList.Add(&bmp,RGB(255,0,255)); bmp.Detach();

bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH10)); m_cMouthList.Add(&bmp,RGB(255,0,255)); bmp.Detach();

bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH11)); m_cMouthList.Add(&bmp,RGB(255,0,255)); bmp.Detach();

bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH12)); m_cMouthList.Add(&bmp,RGB(255,0,255)); bmp.Detach();

bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH13)); m_cMouthList.Add(&bmp,RGB(255,0,255)); bmp.Detach();

bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICEYESNAR)); m_cMouthList.Add(&bmp,RGB(255,0,255)); bmp.Detach();

bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICEYESCLO)); m_cMouthList.Add(&bmp,RGB(255,0,255)); bmp.Detach();

m_cMouthList.SetOverlayImage(1,1); m_cMouthList.SetOverlayImage(2,2); m_cMouthList.SetOverlayImage(3,3); m_cMouthList.SetOverlayImage(4,4); m_cMouthList.SetOverlayImage(5,5); m_cMouthList.SetOverlayImage(6,6); m_cMouthList.SetOverlayImage(7,7); m_cMouthList.SetOverlayImage(8,8); m_cMouthList.SetOverlayImage(9,9); m_cMouthList.SetOverlayImage(10,10); m_cMouthList.SetOverlayImage(11,11); m_cMouthList.SetOverlayImage(12,12); m_cMouthList.SetOverlayImage(13,13); m_cMouthList.SetOverlayImage(14,WEYESNAR); m_cMouthList.SetOverlayImage(15,WEYESCLO);}成員函數(shù)InitText2Speech和InitMouthImageList都在OnInitDialog函數(shù)中被調(diào)用。響應(yīng)朗讀、暫停和繼續(xù)的函數(shù)較簡(jiǎn)單,其代碼如下:voidCReciterDlg::OnButtonSpeak(){ //TODO:Addyourcontrolnotificationhandlercodehere UpdateData();

if(FAILED(m_Text2Speech.Speak(m_strText.AllocSysString(),SPF_ASYNC))) AfxMessageBox(m_Text2Speech.GetErrorString());}

voidCReciterDlg::OnButtonStop(){m_Text2Speech.Pause();}

voidCReciterDlg::OnButtonResume(){ m_Text2Speech.Resume(); }選擇列表中的語(yǔ)音語(yǔ)言并設(shè)為當(dāng)前的語(yǔ)音設(shè)置是通過(guò)響應(yīng)該列表的LBN_SELCHANGE消息來(lái)實(shí)現(xiàn)的。其消息響應(yīng)函數(shù)為:voidCReciterDlg::OnSelchangeList1(){ CStringsVoice; intnIndex=m_ListVoices.GetCurSel(); m_ListVoices.GetText(nIndex,sVoice);

BSTRbstr=sVoice.AllocSysString(); if(FAILED(m_Text2Speech.SetVoice(&bstr))) AfxMessageBox(m_Text2Speech.GetErrorString());}

為了顯示朗讀者肖像,需要將其肖像序列在OnPaint函

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論