SAPI
编辑本段一、SAPI简介
软件中的语音技术包括两方面的内容,一个是语音识别(speech recognition) 和语音合成(speech synthesis)。这两个技术都需要语音引擎的支持。微软推出的应用编程接口API,虽然现在不是业界标准,但是应用比较广泛。 SAPI全称 The Microsoft Speech API.相关的SR和SS引擎位于Speech SDK开发包中。这个语音引擎支持多种语言的识别和朗读,包括英文、中文、日文等。 SAPI包括以下组件对象(接口): (1)Voice Commands API。对应用程序进行控制,一般用于语音识别系统中。识别某个命令后,会调用相关接口是应用程序完成对应的功能。如果程序想实现语音控制,必须使用此组对象。 (2)Voice Dictation API。听写输入,即语音识别接口。 (3)Voice Text API。完成从文字到语音的转换,即语音合成。 (4)Voice Telephone API。语音识别和语音合成综合运用到电话系统之上,利用此接口可以建立一个电话应答系统,甚至可以通过电话控制计算机。 (5)Audio Objects API。封装了计算机发音系统。 SAPI是架构在COM基础上的,微软还提供了ActiveX控件,所以不仅可用于一般的windows程序,还可以用于网页、VBA甚至EXCEL的图表中。如果对COM感到陌生,还可以使用微软的C++ WRAPPERS,它用C++类封装了语音SDK COM对象。编辑本段二、安装SAPI SDK。
首先下载开发包 Microsoft Speech SDK 5.1添加了Automation支持。所以可以在VB,ECMAScript等支持Automation的语言中使用。 版本说明: Version: 5.1 发布日期: 8/8/2001 语音: English 下载尺寸: 2.0 MB - 288.8 MB 这个SDK开发包还包括了可以随便发布的英文和中文的语音合成引擎(TTS),和英文、中文、日文的语音识别引擎(SR)。 系统要求98以上版本。编译开发包中的例子程序需要vc6以上环境。 ******下载说明******: (1)如果要下载例子程序,说明文档,SAPI以及用于开发的美国英语语音引擎,需要下载SpeechSDK51.exe,大约68M。 (2)如果想要使用简体中文和日文的语音引擎,需要下载SpeechSDK51LangPack.exe。大约82M。 (3)如果想要和自己的软件一起发布语音引擎,需要下载SpeechSDK51MSM.exe,大约132M。 (在这个地址,我未能成功下载)。 (4)如果要获取XP下的 Mike 和 Mary 语音,下载Sp5TTIntXP.exe。大约3.5M。 (5)如果要获取开发包的文档说明,请下载sapi.chm。大约2.3M。这个在sdk51里面已经包含。 下载完毕后,首先安装SpeechSDK51.exe,然后安装中文语言补丁包SpeechSDK51LangPack,然后展开 msttss22l,自动将所需dll安装到系统目录。编辑本段三、配置vc环境
在vc6.0的环境下编译语音工程,首先要配置编译环境。假设sdk安装在d:/Microsoft Speech SDK 5.1/路径下,打开工程设置对话框,在c/c++栏中选择Preprocessor分类,然后在"附加包含路径"中输入 d:/Microsoft Speech SDK 5.1/include 告诉vc编译程序所需的SAPI头文件的位置。 然后切换到LINK栏,在Input分类下的附加库路径中输入: d:/Microsoft Speech SDK 5.1/lib/i386 使vc在链接的时候能够找到sapi.lib。编辑本段四、语音合成的应用
。即使用SAPI实现TTS(Text to Speech)。1、首先要初始化语音接口:
ISpVoice* pVoice; ::CoInitialize(NULL); HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice); 然后就可以使用这个指针调用SAPI函数了,例如 pVoice->SetVolume(50);//设置音量 pVoice->Speak(str.AllocSysString(),SPF_ASYNC,NULL); 另外也可以使用如下方式: CComPtr m_cpVoice; HRESULT hr = m_cpVoice.CoCreateInstance( CLSID_SpVoice ); 在下面的例子中都用这个m_cpVoice变量。 CLSID_SpVoice的定义位于SPAI.H中。2、获取/设置输出频率。
SAPI朗读文字的时候,可以采用多种频率方式输出声音,比如: 8kHz 8Bit Mono、8kHz 8Bit Stereo、44kHz 16Bit Mono、44kHz 16Bit Stereo等。在音调上有所差别。具体可以参考sapi.h。 可以使用如下代码获取当前的配置: CComPtr cpStream; HRESULT hrOutputStream = m_cpVoice->GetOutputStream(&cpStream); if (hrOutputStream == S_OK) { CSpStreamFormat Fmt; hr = Fmt.AssignFormat(cpStream); if (SUCCEEDED(hr)) { SPSTREAMFORMAT eFmt = Fmt.ComputeFormatEnum(); } } SPSTREAMFORMAT 是一个ENUM类型,定义位于SPAI.H中。每一个值对应了不同的频率设置。例如 SPSF_8kHz8BitStereo = 5 3 通过如下代码设置当前朗读频率 : CComPtr m_cpOutAudio; //声音输出接口 SpCreateDefaultObjectFromCategoryId( SPCAT_AUDIOOUT, &m_cpOutAudio ); //创建接口 SPSTREAMFORMAT eFmt = 21; //SPSF_22kHz 8Bit Stereo CSpStreamFormat Fmt; Fmt.AssignFormat(eFmt); if ( m_cpOutAudio ) { hr = m_cpOutAudio->SetFormat( Fmt.FormatId(), Fmt.WaveFormatExPtr() ); } else hr = E_FAIL; if( SUCCEEDED( hr ) ) { m_cpVoice->SetOutput( m_cpOutAudio, FALSE ); }3、获取/设置播放所用语音。
引擎中所用的语音数据文件一般保存在SpeechEngines下的spd或者vce文件中。安装sdk后,在注册表中保存了可用的语音,比如英文的男/女,简体中文的男音等。位置是: HKEY_LOCAL_MACHINE/Software/Microsoft/Speech/Voices/Tokens 如果安装在中文操作系统下,则缺省所用的朗读语音是简体中文。SAPI的缺点是不能支持中英文混读,在朗读中文的时候,遇到英文,只能逐个字母读出。所以需要程序自己进行语音切换。 (1) 可以采用如下的函数把当前SDK支持的语音填充在一个组合框中: // SAPI5 helper function in sphelper.h HWND hWndCombo = GetDlgItem( hWnd, IDC_COMBO_VOICES ); //组合框句柄 HRESULT hr = SpInitTokenComboBox( hWndCombo , SPCAT_VOICES ); 这个函数是通过IEnumSpObjectTokens接口枚举当前可用的语音接口,把接口的说明文字添加到组合框中,并且把接口的指针作为LPARAM 保存在组合框中。 一定要记住最后程序退出的时候,释放组合框中保存的接口: SpDestroyTokenComboBox( hWndCombo ); 这个函数的原理就是逐个取得combo里面每一项的LPARAM数据,转换成IUnknown接口指针,然后调用Release函数。 (2) 当组合框选择变化的时候,可以用下面的函数获取用户选择的语音: ISpObjectToken* pToken = SpGetCurSelComboBoxToken( hWndCombo ); (3) 用下面的函数获取当前正在使用的语音: CComPtr pOldToken; HRESULT hr = m_cpVoice->GetVoice( &pOldToken ); (4) 当用户选择的语音和当前正在使用的不一致的时候,用下面的函数修改: if (pOldToken != pToken) { // 首先结束当前的朗读,这个不是必须的。 HRESULT hr = m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, 0); if (SUCCEEDED (hr) ) { hr = m_cpVoice->SetVoice( pToken ); } } (5) 也可以直接使用函数SpGetTokenFromId获取指定voice的Token指针,例如: WCHAR pszTokenId[] = L"HKEY_LOCAL_MACHINE//Software//Microsoft//Speech//Voices//Tokens//MSSimplifiedChineseVoice"; SpGetTokenFromId(pszTokenID , &pChineseToken);4、开始/暂停/恢复/结束当前的朗读
要朗读的文字必须位于宽字符串中,假设位于szWTextString中,则: 开始朗读的代码: hr = m_cpVoice->Speak( szWTextString, SPF_ASYNC | SPF_IS_NOT_XML, 0 ); 如果要解读一个XML文本,用: hr = m_cpVoice->Speak( szWTextString, SPF_ASYNC | SPF_IS_XML, 0 ); 暂停的代码: m_cpVoice->Pause(); 恢复的代码: m_cpVoice->Resume(); 结束的代码:(上面的例子中已经给出了) hr = m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, 0);5、跳过部分朗读的文字
在朗读的过程中,可以跳过部分文字继续后面的朗读,代码如下: ULONG ulGarbage = 0; WCHAR szGarbage[] = L"Sentence"; hr = m_cpVoice->Skip( szGarbage, SkipNum, &ulGarbage ); SkipNum是设置要跳过的句子数量,值可以是正/负。 根据sdk的说明,目前SAPI仅仅支持SENTENCE这个类型。SAPI是通过标点符号来区分句子的。 6、播放WAV文件。SAPI可以播放WAV文件,这是通过ISpStream接口实现的: CComPtr cpWavStream; WCHAR szwWavFileName[NORM_SIZE] = L"";; USES_CONVERSION; wcscpy( szwWavFileName, T2W( szAFileName ) );//从ANSI将WAV文件的名字转换成宽字符串 //使用sphelper.h 提供的这个函数打开 wav 文件,并得到一个 IStream 指针 hr = SPBindToFile( szwWavFileName, SPFM_OPEN_READONLY, &cpWavStream ); if( SUCCEEDED( hr ) ) { m_cpVoice->SpeakStream( cpWavStream, SPF_ASYNC, NULL );//播放WAV文件 }7、将朗读的结果保存到wav文件
TCHAR szFileName[256];//假设这里面保存着目标文件的路径 USES_CONVERSION; WCHAR m_szWFileName[MAX_FILE_PATH]; wcscpy( m_szWFileName, T2W(szFileName) );//转换成宽字符串 //创建一个输出流,绑定到wav文件 CSpStreamFormat OriginalFmt; CComPtr cpWavStream; CComPtr cpOldStream; HRESULT hr = m_cpVoice->GetOutputStream( &cpOldStream ); if (hr == S_OK) hr = OriginalFmt.AssignFormat(cpOldStream); else hr = E_FAIL; // 使用sphelper.h中提供的函数创建 wav 文件 if (SUCCEEDED(hr)) { hr = SPBindToFile( m_szWFileName, SPFM_CREATE_ALWAYS, &cpWavStream, &OriginalFmt.FormatId(), OriginalFmt.WaveFormatExPtr() ); } if( SUCCEEDED( hr ) ) { //设置声音的输出到 wav 文件,而不是 speakers m_cpVoice->SetOutput(cpWavStream, TRUE); } //开始朗读 m_cpVoice->Speak( szWTextString, SPF_ASYNC | SPF_IS_NOT_XML, 0 ); //等待朗读结束 m_cpVoice->WaitUntilDone( INFINITE ); cpWavStream.Release(); //把输出重新定位到原来的流 m_cpVoice->SetOutput( cpOldStream, FALSE );8、设置朗读音量和速度
m_cpVoice->SetVolume((USHORT)hpos); //设置音量,范围是 0 - 100 m_cpVoice->SetRate(hpos); //设置速度,范围是 -10 - 10 hpos的值一般位于9、设置SAPI通知消息
。SAPI在朗读的过程中,会给指定窗口发送消息,窗口收到消息后,可以主动获取SAPI的事件, 根据事件的不同,用户可以得到当前SAPI的一些信息,比如正在朗读的单词的位置,当前的朗读口型值(用于显 示动画口型,中文语音的情况下并不提供这个事件)等等。 要获取SAPI的通知,首先要注册一个消息: m_cpVoice->SetNotifyWindowMessage( hWnd, WM_TTSAPPCUSTOMEVENT, 0, 0 ); 这个代码一般是在主窗口初始化的时候调用,hWnd是主窗口(或者接收消息的窗口)句柄。WM_TTSAPPCUSTOMEVENT 是用户自定义消息。 在窗口响应WM_TTSAPPCUSTOMEVENT消息的函数中,通过如下代码获取sapi的通知事件: CSpEvent event; // 使用这个类,比用 SPEVENT结构更方便 while( event.GetFrom(m_cpVoice) == S_OK ) { switch( event.eEventId ) { 。。。 } } eEventID有很多种,比如SPEI_START_INPUT_STREAM表示开始朗读,SPEI_END_INPUT_STREAM表示朗读结束等。 可以根据需要进行判断使用。编辑本段四、结束语
SAPI的功能很多,比如语音识别、使用语法分析等,由于条件和精力有限,我未能一一尝试,感兴趣的朋友可以自己安装一个研究一下。-------------------------------------------------------------------------------------------------------------------------------------------------
关于SAPI的简介
API 概述
SAPI API在一个应用程序和语音引擎之间提供一个高级别的接口。SAPI 实现了所有必需的对各种语音引擎的实时的控制和管理等低级别的细节。
SAPI引擎的两个基本类型是文本语音转换系统(TTS)和语音识别系统。TTS系统使用合成语音合成文本字符串和文件到声音音频流。语音识别技术转换人类的声音语音流到可读的文本字符串或者文件。
文本语音转换API
应用程序能通过IspVoice的对象组建模型(COM)接口控制文本语音转换。一旦一个应用程序有一个已建立的IspVoice对象(见Text-to-Speech指南),这个应用程序就只需要调用ISpVoice::Speak 就可以从文本数据得到发音。另外,ISpVoice接口也提供一些方法来改变声音和合成属性,如语速ISpVoice::SetRate,输出音量ISpVoice::SetVolume,改变当前讲话的声音ISpVoice::SetVoice等。
特定的SAPI控制器也可以嵌入输入文本使用来实时的改变语音合成器的属性,如声音,音调,强调字,语速和音量。这些合成标记在sapi.xsd中,使用标准的XML格式,这是一个简单但很强大定制TTS语音的方法,不依赖于特定的引擎和当前使用的声音。
ISpVoice::Speak方法能够用于同步的(当完全的完成朗读后才返回)或异步的(立即返回,朗读在后台处理)操作。当同步朗读(SPF_ASYNC)时,实时的状态信息如朗读状态和当前文本位置可以通过ISpVoice::GetStatus得到。当异步朗读时,可以打断当前的朗读输出以朗读一个新文本或者把新文本自动附加在当前朗读输出的文本的末尾。
除了ISpVoice接口之外SAPI也为高级TTS应用程序提供许多有用的COM接口。
事件
SAPI用标准的回调机制(Window消息, 回调函数 or Win32 事件)来发送事件来和应用程序通信。对于TTS,事件大多用于同步地输出语音。应用程序能够与它们发生的实时行为例如单词边界,音素,口型或者应用程序定制的书签等同步。应用程序能够用ISpNotifySource, ISpNotifySink, ISpNotifyTranslator, ISpEventSink, ISpEventSource, 和 ISpNotifyCallback初始化和处理这些实时事件。
字典
应用程序通过使用ISpContainerLexicon, ISpLexicon 和IspPhoneConverter提供的方法能为语音合成引擎提供定制的单词发音。
资源
查找和选择SAPI语音数据如声音文件及发音字典可以被下列COM接口控制:ISpDataKey, ISpRegDataKey, ISpObjectTokenInit, ISpObjectTokenCategory, ISpObjectToken, IEnumSpObjectTokens, ISpObjectWithToken, ISpResourceManager 和 IspTask。
音频
最后,有一个接口能把声音输出到一些指定目标如电话和自定硬件 (ISpAudio, ISpMMSysAudio, ISpStream, ISpStreamFormat, ISpStreamFormatConverter)。
语音识别 API
就像ISpVoice是语音合成的主接口,IspRecoContext是语音识别的主接口。像ISpVoice一样,它是一个IspEventSource接口,这意味着它是语音程序接收被请求的语音识别事件通知的媒介。
一个应用程序必须从两个不同类型的语音识别引擎(ISpRecognizer)中选择一种。一种是可以与其它语音识别程序共享识别器的语音识别引擎,这在大多数识别程序中被推荐使用。为了为IspRecognizer建立一个共享的ISpRecoContext接口,一个应用程序只需要用CLSID_SpSharedRecoContext调用COM的 CoCreateInstance方法。这种方案中,SAPI将建立一个音频输入流,把它设置为SAPI默认的音频输入流。对于大型服务器程序,它可能在单独在一个系统上运行,性能是关键,一个InProc语音识别引擎更适合。
为了为InProc ISpRecognizer建立一个IspRecoContext,程序必须首先用CLSID_SpInprocRecoInstance调用CoCreateInstance来建立属于它自己的InProc IspRecognizer。然后程序必须调用ISpRecognizer::SetInput(见 also ISpObjectToken)来建立一个音频输入流。最后程序可以调用ISpRecognizer::CreateRecoContext来得到一个IspRecoContext。
下一步是建立程序感兴趣的事件通知,因为IspRecognizer也是一个IspEventSource,IspEventSource实际上是IspNotifySource,程序从它的ISpRecoContext可以调用IspNotifySource的一个方法来指出IspRecoContext的哪里的事件应该被报告。然后它应该调用ISpEventSource::SetInterest来指出哪些事件应该通报。最重要的事件是SPEI_RECOGNITION,指出和IspRecoContext相关的IspRecognizer已经识别了一些语音。其他可用到的语音识别事件的详细资料参见SPEVENTENUM。
最后,一个语音程序必须建立,加载,并且激活一个IspRecoGrammar,本质上就是指出哪些类型的发言被识别,例如口述或一个命令和控制文法。首先,程序用ISpRecoContext::CreateGrammar建立一个IspRecoGrammar,然后程序加载适合的文法,下面两个方法中调用其中一个:口述模式的调用方法ISpRecoGrammar::LoadDictation,命令和控制模式的则调用方法ISpRecoGrammar::LoadCmdxxx。最后为了激活这些文法以开始进行识别,程序为口述模式调用ISpRecoGrammar::SetDictationState或者为命令和控制模式调用调用ISpRecoGrammar::SetRuleState或者ISpRecoGrammar::SetRuleIdState。
当识别依靠通知机制返回到程序,SPEVENT结构的成员lParam将是一个IspRecoResult,程序可以确定什么被识别和使用了IspRecoContext的哪个IspRecoGrammar。
一个IspRecognizer,无论是否是共享的还是InProc的,都可以有多个IspRecoContexts和它关联,并且每个都可以通过它自己的事件通知方法通知IspRecognizer。从一个IspRecoContext可以建立多个IspRecoGrammars,以便于识别不同类型的发言。
利用微软Speech SDK 5.1在MFC中进行语音识别开发时的主要步骤,以Speech API 5.1+VC6为例:
1、初始化COM端口
一般在CWinApp的子类中,调用CoInitializeEx函数进行COM初始化,代码如下:
::CoInitializeEx(NULL,COINIT_APARTMENTTHREADED); // 初始化COM
注意:调用这个函数时,要在工程设置(project settings)->C/C++标签,Category中选Preprocessor,在Preprocessor definitions:下的文本框中加上“,_WIN32_DCOM”。否则编译不能通过。
2、创建识别引擎
微软Speech SDK 5.1 支持两种模式的:共享(Share)和独享(InProc)。一般情况下可以使用共享型,大的服务型程序使用InProc。如下:
hr = m_cpRecognizer.CoCreateInstance(CLSID_SpSharedRecognizer);//Share
hr = m_cpRecognizer.CoCreateInstance(CLSID_SpInprocRecognizer);//InProc
如果是Share型,可直接进到步骤3;如果是InProc型,必须使用 ISpRecognizer::SetInput 设置语音输入。如下:
CComPtr<ISpObjectToken> cpAudioToken; //定义一个token
hr = SpGetDefaultTokenFromCategoryId(SPCAT_AUDIOIN, &cpAudioToken); //建立默认的音频输入对象
if (SUCCEEDED(hr)) { hr = m_cpRecognizer->SetInput(cpAudioToken, TRUE);}
或者:
CComPtr<ISpAudio> cpAudio; //定义一个音频对象
hr = SpCreateDefaultObjectFromCategoryId(SPCAT_AUDIOIN, &cpAudio);//建立默认的音频输入对象
hr = m_cpRecoEngine->SetInput(cpAudio, TRUE);//设置识别引擎输入源
3、创建识别上下文接口
调用 ISpRecognizer::CreateRecoContext 创建识别上下文接口(ISpRecoContext),如下:
hr = m_cpRecoEngine->CreateRecoContext( &m_cpRecoCtxt );
4、设置识别消息
调用 SetNotifyWindowMessage 告诉Windows哪个是我们的识别消息,需要进行处理。如下:
hr = m_cpRecoCtxt->SetNotifyWindowMessage(m_hWnd, WM_RECOEVENT, 0, 0);
SetNotifyWindowMessage 定义在 ISpNotifySource 中。
5、设置我们感兴趣的事件
其中最重要的事件是”SPEI_RECOGNITION“。参照 SPEVENTENUM。代码如下:
const ULONGLONG ullInterest = SPFEI(SPEI_SOUND_START) | SPFEI(SPEI_SOUND_END) | SPFEI(SPEI_RECOGNITION) ;
hr = m_cpRecoCtxt->SetInterest(ullInterest, ullInterest);
6、创建语法规则
语法规则是识别的灵魂,必须要设置。分为两种,一种是听说式(dictation),一种是命令式(command and control---C&C)。首先 利用ISpRecoContext::CreateGrammar 创建语法对象,然后加载不同的语法规则,如下:
//dictation
hr = m_cpRecoCtxt->CreateGrammar( GIDDICTATION, &m_cpDictationGrammar );
if (SUCCEEDED(hr))
{
hr = m_cpDictationGrammar->LoadDictation(NULL, SPLO_STATIC);//加载词典
}
//C&C
hr = m_cpRecoCtxt->CreateGrammar( GIDCMDCTRL, &m_cpCmdGrammar);
然后利用ISpRecoGrammar::LoadCmdxxx 加载语法,例如从CmdCtrl.xml中加载:
WCHAR wszXMLFile[20]=L"";
MultiByteToWideChar(CP_ACP, 0, (LPCSTR)"CmdCtrl.xml" , -1, wszXMLFile, 256);//ANSI转UNINCODE
hr = m_cpCmdGrammar->LoadCmdFromFile(wszXMLFile,SPLO_DYNAMIC);
注意:C&C时,语法文件使用xml格式,参见Speech SDK 5.1 中的 Designing Grammar Rules。简单例子:
<GRAMMAR LANGID="804">
<DEFINE>
<ID NAME="CMD" VAL="10"/>
</DEFINE>
<RULE NAME="COMMAND" ID="CMD" TOPLEVEL="ACTIVE">
<L>
<p>尹成</P>
<p>山东大学</p>
<p>中科院</p>
</L>
</RULE>
</GRAMMAR>
LANGI*="804"代表简体中文,在<*>...</*>中增加命令。
7、在开始识别时,激活语法进行识别
hr = m_cpDictationGrammar->SetDictationState( SPRS_ACTIVE );//dictation
hr = m_cpCmdGrammar->SetRuleState( NULL,NULL,SPRS_ACTIVE );//C&C
8、获取识别消息,进行处理
截获识别消息(WM_RECOEVENT),然后处理。识别的结果放在CSpEvent的ISpRecoResult 中。如下:
USES_CONVERSION;
CSpEvent event;
switch (event.eEventId)
{
case SPEI_RECOGNITION:
{
//识别出了语音输入
m_bGotReco = TRUE;
static const WCHAR wszUnrecognized[] = L"<Unrecognized>";
CSpDynamicString dstrText;
//取得识别结果
if (FAILED(event.RecoResult()->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, TRUE ,&dstrText, NULL)))
{
dstrText = wszUnrecognized;
}
BSTR SRout;
dstrText.CopyToBSTR(&SRout);
CString Recstring;
Recstring.Empty();
Recstring = SRout;
//进一步处理
......
}
break;
}
9、释放创建的引擎、识别上下文对象、语法等。调用相应的Release函数即可。
本文来自CSDN博客 :http://blog.csdn.net/yincheng01/archive/2010/02/20/5313204.aspx
Windows 语音识别编程
语音类
1)在构造语音类之前,必须先设置好工程环境:
a、从微软官方网站下载windows speech sdk并安装,然后在Visual Studio 6.0中进行相关设置,在Project Setings选项的C++选项卡的“分类:预处理器”添加“,__WIN32_DCOM”(为预先初始化COM组件成功);
b、将预处理头文件选项选中“自动使用预补偿页眉”;
c、在常规选项卡中选择“实用MFC静态连接库”;
入图:(一)
2)封装语音类
由于采用面向对象的编程理念,借助UML(Unified Modeling Language统一建模语言)构造CSPEECH语音类如下
CSPEECH类
+ void InitSR(); //初始化语音
+void RecoEvent();//识别命令函数
+BOOL b_initSR;
+BOOL b_Cmd_Grammar;
//3个语音接口
+CComPtr m_cpRecocontxt;
+CComPtr m_cpRecoGrammar;
+CComPtr m_cpRecoEngine;
然后开始添加语音类,需要注意的是在定义语音类的头文件中,包含〈sphelper.h〉并且自定义语音识别消息和类型
#define GID_CMD_GR 333333
#define WM_RECOEVENT WM_USER+102
剩下来就是对cpp文件的函数initSR()和RecoEvent()补充函数体
3)具体见下面代码:
(1)void CSpeech::initSR()
{
HRESULT hr=S_OK;
hr=m_cpRecoEngine.CoCreateInstance(CLSID_SpInprocRecognizer);//创建识别引擎COM实例
if(SUCCEEDED(hr))
{
hr =m_cpRecoEngine->CreateRecoContext(&m_cpRecoCtxt );//创建识别上下文
}
else
MessageBox(hWnd,"error1","error",S_OK);
if(SUCCEEDED(hr))
{
hr = m_cpRecoCtxt->SetNotifyWindowMessage(hWnd, WM_RECOEVENT, 0, 0 );
}//消息机制设置,使计算机时刻监听语音消息
else
MessageBox(hWnd,"error2","error",S_OK);
if (SUCCEEDED(hr))
{
ULONGLONG ullMyEvents = SPFEI(SPEI_RECOGNITION) | SPFEI(SPEI_HYPOTHESIS);
hr = m_cpRecoCtxt->SetInterest(ullMyEvents, ullMyEvents);
}
else
MessageBox(hWnd,"error3","error",S_OK);
//设置默认的音频
CComPtr m_cpAudio;
hr=SpCreateDefaultObjectFromCategoryId(SPCAT_AUDIOIN,&m_cpAudio);//建立默认的音频输入对象
hr=m_cpRecoEngine->SetInput(m_cpAudio,TRUE);//设置识别引擎输入源
hr=m_cpRecoCtxt->CreateGrammar(GID_CMD_GR,&m_cpCmdGrammar);//创建命令语法
b_Cmd_Grammar=TRUE;
if(FAILED(hr))
{
MessageBox(hWnd,"error 4","error",S_OK);
}
hr=m_cpCmdGrammar->LoadCmdFromResource(NULL,MAKEINTRESOURCEW(IDR_CMDCTRL),L"SRGRAMMAR",MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL), SPLO_DYNAMIC);//加载命令语法文件
if(FAILED(hr))
{
MessageBox(hWnd,"error5","error",S_OK);
}
b_initSR=TRUE;
}
(2)BOOL CSpeech::RecoEvent()
{
USES_CONVERSION;
CSpEvent event;
while(event.GetFrom(m_cpRecoCtxt)==S_OK)
{
switch(event.eEventId)
{
case SPEI_RECOGNITION:
{
static const WCHAR wszUnrecognized[]=L"";
CSpDynamicString dstrText;
if(FAILED(event.RecoResult()->GetText(SP_GETWHOLEPHRASE,SP_GETWHOLEPHRASE,TRUE,&dstrText,NULL)))
{
dstrText=wszUnrecognized;
}
dstrText.CopyToBSTR(&SRout);
Recstring.Empty();
Recstring=SRout;
if(b_Cmd_Grammar)
{
if(Recstring=="左")
{
ISpVoice *pVoice=NULL;
if(FAILED(CoInitialize(NULL)))
{
MessageBox(hWnd,"Error to initialize COM","error",S_OK);
return FALSE;
}
HRESULT hr=CoCreateInstance(CLSID_SpVoice,NULL,CLSCTX_ALL,IID_ISpVoice,(void**)&pVoice);
if(SUCCEEDED(hr))
{
hr=pVoice->Speak(L"左转",0,NULL);
pVoice->Release();
pVoice=NULL;
}
CoUninitialize();
m_OpenGL->m_baiscobj->LEFT=1;
return TRUE ;
}
if(Recstring=="向下走")
{
ISpVoice *pVoice=NULL;
if(FAILED(CoInitialize(NULL)))
{
MessageBox(hWnd,"Error to initialize COM","error",S_OK);
return FALSE;
}
HRESULT hr=CoCreateInstance(CLSID_SpVoice,NULL,CLSCTX_ALL,IID_ISpVoice,(void**)&pVoice);
if(SUCCEEDED(hr))
{
hr=pVoice->Speak(L"开始后退",0,NULL);
pVoice->Release();
pVoice=NULL;
}
CoUninitialize();
m_OpenGL->m_baiscobj->BACK=1;
return TRUE ;
}
if(Recstring=="最小化")
{
ISpVoice *pVoice=NULL;
if(FAILED(CoInitialize(NULL)))
{
MessageBox(hWnd,"Error to initialize COM","error",S_OK);
return FALSE;
}
HRESULT hr=CoCreateInstance(CLSID_SpVoice,NULL,CLSCTX_ALL,IID_ISpVoice,(void**)&pVoice);
if(SUCCEEDED(hr))
{
hr=pVoice->Speak(L"最小化",0,NULL);
pVoice->Release();
pVoice=NULL;
}
CoUninitialize();
SendMessage(hWnd,WM_SYSCOMMAND, SC_MINIMIZE, MAKELPARAM(0, 0));
return TRUE;
}
if(Recstring=="右")
{
ISpVoice *pVoice=NULL;
if(FAILED(CoInitialize(NULL)))
{
MessageBox(hWnd,"Error to initialize COM","error",S_OK);
return FALSE;
}
HRESULT hr=CoCreateInstance(CLSID_SpVoice,NULL,CLSCTX_ALL,IID_ISpVoice,(void**)&pVoice);
if(SUCCEEDED(hr))
{
hr=pVoice->Speak(L"开始右转",0,NULL);
pVoice->Release();
pVoice=NULL;
}
CoUninitialize();
m_OpenGL->m_baiscobj->RIGHT=1;
return TRUE ;
}
if(Recstring=="停下来")
{
ISpVoice *pVoice=NULL;
if(FAILED(CoInitialize(NULL)))
{
MessageBox(hWnd,"Error to initialize COM","error",S_OK);
return FALSE;
}
HRESULT hr=CoCreateInstance(CLSID_SpVoice,NULL,CLSCTX_ALL,IID_ISpVoice,(void**)&pVoice);
if(SUCCEEDED(hr))
{
hr=pVoice->Speak(L"动作开始了",0,NULL);
pVoice->Release();
pVoice=NULL;
}
CoUninitialize();
m_OpenGL->m_baiscobj->Move=0;
m_OpenGL->m_baiscobj->BACK=0;
m_OpenGL->m_baiscobj->LEFT=0;
m_OpenGL->m_baiscobj->RIGHT=0;
return TRUE ;
}
if(Recstring=="跑步")
{
ISpVoice *pVoice=NULL;
if(FAILED(CoInitialize(NULL)))
{
MessageBox(hWnd,"Error to initialize COM","error",S_OK);
return FALSE;
}
HRESULT hr=CoCreateInstance(CLSID_SpVoice,NULL,CLSCTX_ALL,IID_ISpVoice,(void**)&pVoice);
if(SUCCEEDED(hr))
{
hr=pVoice->Speak(L"动作开始了",0,NULL);
pVoice->Release();
pVoice=NULL;
}
CoUninitialize();
m_OpenGL->m_baiscobj->Move=1;
return TRUE ;
}
if(Recstring=="退出")
{
m_OpenGL->CleanUp(); // 结束处理
PostQuitMessage(0);
return TRUE;
}
}
}
}
}return TRUE;
}
要注意的是RecoEvent()必须能处理人物、摄像头的漫游,所以在人物、摄像机类的行为函数中添加了控制变量Move、BACK、LEFT、RIGHT;并附了初值1,当在行为函数中为1时行为函数体执行,所以也必须
#include "OpenGL.h"
#include "baiscobj.h"
其间我们借助于指针变量,巧妙的使语音能控制行为,却不影响动画的刷新,但不足的是由于opengl动画md2模型的不能导入成功,使踢球,跳木箱等功能函数没有完成,所以只要行为函数出来,可通过上述同样方法实现语音控制。
4)如何在winmain()函数中执行语音程序?
首先包含语音头文件〈sapi.h〉
接着(#define CSpeech speech)定义语音类对象
INT WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR,INT )// WinMain程序入口
{
::CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);//初始化COM
……
char cc[]="tml";
WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
GetModuleHandle(NULL), NULL, NULL, NULL, (LPCTSTR)IDR_MENU1,
cc, NULL };
RegisterClassEx( &wc );
m_OpenGL=new OpenGL();//
hWnd = CreateWindowEx(NULL,cc,"智能精灵键盘(↑进↓退→右←左UP仰DOWM俯)",
dwStyle|WS_CLIPCHILDREN|WS_CLIPSIBLINGS,nX,nY,Width, Height,NULL,NULL,hInst,NULL); // 创建窗口
ShowWindow( hWnd, SW_SHOWDEFAULT ); // 显示窗口
UpdateWindow( hWnd ); // 刷新窗口
speech.b_Cmd_Grammar=FALSE;
speech.initSR();
GameLoop(); // 进入消息循环
return 0;
}
通过speech.initSR(),执行语音的初始化,为了设置一个简单的语音识别开关,简单的添加一个任务栏,只有语音这一个菜单资源,然后利用消息机制,在消息处理函数里Switch(message)里添加:
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDM_SPEECH:speech.startcmd();
}
return 0;break;
即当单击语音菜单时,则使语音功能完全激活,下面是这个函数的实体:
void CSpeech::startcmd()
{
if(b_initSR)
{
HRESULT hr=m_cpCmdGrammar->SetRuleState(NULL,NULL,SPRS_ACTIVE);
ISpVoice *pVoice=NULL;
if(FAILED(CoInitialize(NULL)))
{
MessageBox(hWnd,"Error to initialize COM","error",S_OK);
return ;
}
hr=CoCreateInstance(CLSID_SpVoice,NULL,CLSCTX_ALL,IID_ISpVoice,(void**)&pVoice);
if(SUCCEEDED(hr))
{
hr=pVoice->Speak(L"语法已经激活",0,NULL);
pVoice->Release();
pVoice=NULL;
}
CoUninitialize();
}
}
5)在所有的工作完成之前,还必须先在项目工程下添加XML语法文件,通过initSR()中的LoadCmdFromResource()函数加载;XML文件可通过以记事本形式打开编辑。具体如下:
---
下
左
右
向上走
向下走
跳
停下来
跑步
识别
语音
还原
文件
踢球