VC 下Microsoft Speech SDK开发语音识别

        VC 下Microsoft Speech SDK开发语音识别                       

                               

    转自VC 下Microsoft Speech SDK开发语音识别

    分类: vc/c++ 824人阅读 评论(0) 收藏 举报

    1.首先开发得需要Microsoft Speech SDK的支持,以下是下载地址

      http://msdn.microsoft.com/code/sample.asp?url=/msdn-files/027/000/781/msdncompositedoc.xml   
      Speech  SDK   5.1   (68   MB)     
      http://download.microsoft.com/download/speechSDK/SDK/5.1/WXP/EN-US/speechsdk51.exe   
      5.1   Language   Pack   (81.5   MB)     
      http://download.microsoft.com/download/speechSDK/SDK/5.1/WXP/EN-US/speechsdk51LangPack.exe   
      Redistributables   (128   MB)     
      http://download.microsoft.com/download/speechSDK/SDK/5.1/WXP/EN-US/speechsdk51MSM.exe   
      Documentation   (2.28   MB)     
      http://download.microsoft.com/download/speechSDK/SDK/5.1/WXP/EN-US/sapi.chm

    2.下载后,执行安装

    下载完毕后首先安装SpeechSDK51.exe,然后安装中文语言补丁包SpeechSDK51LangPack,然后展开
    speechsdk51MSM.exe,这些都是自解压文件,解压后执行相应的setup程序到你要的目录,默认C:/Microsoft Speech SDK 5.1.对应的开发参考手册为sapi.chm,详细描述了各个函数的细节等.

    3.VC的环境配置

    在应用SDK的开发前当然得需要对工程环境进行配置,我用的是VS2003(其他情况类似),配置的过程如下:

    工具->选项->项目->VC++目录,在"显示以下内容的目录"下拉框中选择"包含目录"项,添加一项C:/Program   Files/Microsoft   Speech   SDK   5.1/Include到目录中去。再选择"库文件"项,添加一项C:/Program   Files/Microsoft   Speech   SDK   5.1/Lib/i386到目录中去.

    4.其他准备项

    基础的配置已经完成,那么接下来的工作就是要包含编译的头文件了,所以先将头文件和库文件包含进来

    #include <sapi.h>
    #include <sphelper.h>
    #include <spuihelp.h>

    #pragma comment(lib,"ole32.lib")   //CoInitialize CoCreateInstance需要调用ole32.dll
    #pragma comment(lib,"sapi.lib")    //sapi.lib在SDK的lib目录,必需正确配置

    具体其他函数所需要的头文件可参考sapi.chm手册.

    5.源文件修改项

    看上去上面的部分配置完成后就大功告成了,其实还不全是,当你编译时就会出错:

    c:/program files/microsoft speech sdk 5.1/include/sphelper.h(769) : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
    c:/program files/microsoft speech sdk 5.1/include/sphelper.h(1419) : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
    c:/program files/microsoft speech sdk 5.1/include/sphelper.h(2373) : error C2065: 'psz' : undeclared identifier
    c:/program files/microsoft speech sdk 5.1/include/sphelper.h(2559) : error C2440: 'initializing' : cannot convert from 'CSpDynamicString' to 'SPPHONEID *'
     No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
    c:/program files/microsoft speech sdk 5.1/include/sphelper.h(2633) : error C2664: 'wcslen' : cannot convert parameter 1 from 'SPPHONEID *' to 'const wchar_t *'
    Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
    Speech代码编写时间太早,语法不严密。而VS2003(及以上)对于语法检查非常严格,导致编译无法通过。修改头文件中的以下行即可正常编译:
    Line 769
       修改前: const ulLenVendorPreferred = wcslen(pszVendorPreferred);
       修改后: const unsigned long ulLenVendorPreferred = wcslen(pszVendorPreferred);
    Line 1418
        修改前: static CoMemCopyWFEX(const WAVEFORMATEX * pSrc, WAVEFORMATEX ** ppCoMemWFEX)
        修改后: static HRESULT CoMemCopyWFEX(const WAVEFORMATEX * pSrc, WAVEFORMATEX ** ppCoMemWFEX)
    Line 2372
        修改前: for (const WCHAR * psz = (const WCHAR *)lParam; *psz; psz++) {}
        修改后: const WCHAR * psz; for (psz = (const WCHAR *)lParam; *psz; psz++) {}
    Line 2559
        修改前: SPPHONEID* pphoneId = dsPhoneId;
        修改后: SPPHONEID* pphoneId = (SPPHONEID*)((WCHAR *)dsPhoneId);
    Line 2633
        修改前: pphoneId += wcslen(pphoneId) + 1;
        修改后: pphoneId += wcslen((const wchar_t *)pphoneId) + 1;
    好了,编译通过,下面可以正式编写程序了。
    6:语音朗读: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<ISpVoice>  m_cpVoice;
       HRESULT  hr = m_cpVoice.CoCreateInstance(CLSID_SpVoice );
       在下面的例子中都用这个m_cpVoice变量。CLSID_SpVoice的定义位于sapi.h中。

    • 2.  获取/设置输出频率。

      SAPI朗读文字的时候,可以采用多种频率方式输出声音,比如:8kHz 8Bit Mono、8kHz 8BitStereo、44kHz 16BitStereo等,在音调上有所差别。具体可以参考sapi.h。

       可以使用如下代码获取当前的频率配置:
      CComPtr<ISpStreamFormat> 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类型,定义位于sapi.h中,这样eFmt就保存了获得的当前频率设置值。每一个值对应了不同的频率设置。

       通过如下代码设置当前朗读频率:
       CComPtr<ISpAudio>  m_cpOutAudio; //声音输出接口
       SpCreateDefaultObjectFromCategoryId( SPCAT_AUDIOOUT,&m_cpOutAudio ); //创建接口

       SPSTREAMFORMAT eFmt = SPSF_8kHz8BitMono; //SPSF_8kHz 8Bit Mono这个参数可以参考sapi.chm手册

       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支持的语音填充在一个组合框中:
        // SAPI5helper function in sphelper.h

         CWnd* m_wnd = GetDlgItem(IDC_COMBO_VOICES);
         HWND  hWndCombo = m_wnd->m_hWnd; //组合框句柄
         HRESULT hr =SpInitTokenComboBox( hWndCombo , SPCAT_VOICES );
       这个函数是通过IEnumSpObjectTokens接口枚举当前可用的语音接口,把接口的说明文字添加到组合框中,并且把接口的指针作为LPARAM 保存在组合框中。
       一定要记住最后程序退出的时候,释放组合框中保存的接口:
       SpDestroyTokenComboBox( hWndCombo );
       这个函数的原理就是逐个取得combo里面每一项的LPARAM数据,转换成IUnknown接口指针,然后调用Release函数。
    (2) 当组合框选择变化的时候,可以用下面的函数获取用户选择的语音:
       ISpObjectToken* pToken = SpGetCurSelComboBoxToken( hWndCombo );

    (3) 用下面的函数获取当前正在使用的语音:
       CComPtr<ISpObjectToken> 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  开始/暂停/恢复/结束当前的朗读

    要朗读的文字必须位于宽字符串中,所以从文本框中读取的字符串类型CString必须转换成为WCHAR型,如下(m_strText为文本框变量):
       CString strSpeak;
     m_strText.GetWindowText(strSpeak);
     WCHAR   wChar[256];  
     memset(wChar ,0,256);
     MultiByteToWideChar( CP_ACP , 0 , strSpeak , strSpeak.GetLength() , wChar , 256);
      这样就将文本框中的字符串strSpeak转化为WCHAR型的wChar变量中了.
       开始朗读的代码:
       hr =m_cpVoice->Speak( wChar, SPF_ASYNC |SPF_IS_NOT_XML, 0 );
       如果要解读一个XML文本,用:
       hr =m_cpVoice->Speak( wChar, 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<ISpStream>   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文件
         TCHARszFileName[256];//假设这里面保存着目标文件的路径
         USES_CONVERSION;
         WCHAR m_szWFileName[MAX_FILE_PATH];
         wcscpy( m_szWFileName,T2W(szFileName) );//转换成宽字符串

       //创建一个输出流,绑定到wav文件
       CSpStreamFormat  OriginalFmt;
      CComPtr<ISpStream> cpWavStream;
      CComPtr<ISpStreamFormat>   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( wChar, 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
    • 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表示朗读结束等。
       可以根据需要进行判断使用。

     

     

     

    7:语音识别

    利用微软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函数即可。

     

     

    参考:

     

    http://blog.csdn.net/yincheng01/archive/2010/02/20/5313204.aspx

    http://blog.csdn.net/buaalei/archive/2010/03/12/5372544.aspx

    • 0
      点赞
    • 1
      收藏
      觉得还不错? 一键收藏
    • 0
      评论

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值