ldjhust

工欲善其事 必先利其器

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

转载:http://billxia.diandian.com/post/2012-12-23/40049402032

 

在Windows下,使用Microsoft Speech API(简称为SAPI)可以很简单高效的实现语音识别,关于如何使用SAPI实现语音识别的文章请参见MVP尹成的博客 : 

微软语音识别语音朗读技术

 

VC++基于微软语音引擎开发语音识别总结


而Speech SDK安装后有一个Samples文件夹,里面有C++/C#/VB的示例代码可以参考。 
现在我想把基于SAPI的语音识别转移到我的QT的项目里,也就是在QT里调用微软的SAPI来实现语音识别。这个想法是很简单的,但要实现的话,却充满了阻碍。我首先查了一下QT调用Win API的可能性,发现这是完全可以的;接下来就着手来实现了。 
首次还是封装一个Speech Recognition的引擎类SREngine,其头文件和源文件分别如下: 

/****************************************************

 

   SREngine类,将MS SAPI的语音识别引擎封装,用于语音识别

 

SREngine.h

 

****************************************************/

 

#ifndef SRENGINE_H

 

#define SRENGINE_H

 


 

#include <QString.h>

 

#include <QMessageBox>

 


 

// Microsoft Speech API

 

#undef UNICODE

 

#include <sapi.h>

 

#include <sphelper.h>

 

#include <spuihelp.h>

 

#include <comdef.h>

 

#define UNICODE

 


 

#include <windows.h>

 

#include <windowsx.h>

 

#include <commctrl.h>

 


 

#define  WM_RECOEVENT WM_USER+100

 

#define GID_SRCMD_CN 1234

 

#define MYGRAMMARID  101

 


 

class SREngine

 

{

 

public:

 

    SREngine();

 

    ~SREngine();

 


 

public:

 

    //speech varibale

 

    CComPtr <ISpRecognizer> m_cpRecognizer;

 

    CComPtr <ISpRecoContext> m_cpRecoContext;

 

    CComPtr <ISpRecoGrammar> m_cpCmdGrammar;

 


 

    //audio variable

 

    CComPtr <ISpAudio> m_cpAudio;

 


 

public:

 

    HRESULT SetRuleState(const WCHAR * pszRuleName, const WCHAR *pszValue, BOOL fActivate);

 

    HRESULT LoadCmdFromFile(QString XMLFileName);

 

    HRESULT InitializeSapi(WId hWnd, UINT Msg);

 


 

};

 


 

#endif // SRENGINE_H

 


 

/*****************************************************************

 

   SREngine.cpp

 

*****************************************************************/

 

#include "SREngine.h"

 


 

SREngine::SREngine()

 

{

 

}

 


 

SREngine::~SREngine()

 

{

 

}

 


 

HRESULT SREngine::InitializeSapi(WId hWnd, UINT Msg)

 

{

 

    HRESULT hr = S_OK;

 

    //FOR ONE NOT FOR ALL

 

    /* 独享模式的配置 */

 

    hr = m_cpRecognizer.CoCreateInstance( CLSID_SpInprocRecognizer);  //独享模式

 

    if(FAILED(hr))

 

    {

 

        QMessageBox::information(NULL, "Error", "Create recognizer error", MB_OK);

 

        return hr;

 

    }

 


 

    hr = SpCreateDefaultObjectFromCategoryId(SPCAT_AUDIOIN, &m_cpAudio); //建立默认的音频输入对象

 

    if(FAILED(hr))

 

    {

 

        QMessageBox::information(NULL, "Error", "Create default audio object error", MB_OK);

 

        return hr;

 

    }

 


 

    hr = m_cpRecognizer ->SetInput(m_cpAudio, TRUE);  //设置识别引擎输入源

 

    if(FAILED(hr))

 

    {

 

        QMessageBox::information(NULL, "Error", "Error setINPUT", MB_OK);

 

        return hr;

 

    }

 


 

    hr = m_cpRecognizer->CreateRecoContext(&m_cpRecoContext);   //创建识别上下文接口

 

    if(FAILED(hr))

 

    {

 

        QMessageBox::information(NULL, "Error", "Error CreateRecoContext", MB_OK);

 

        return hr;

 

    }

 

    hr =  m_cpRecoContext->SetNotifyWindowMessage(hWnd, Msg, 0, 0);  //设置识别消息,即将Msg消息绑定到hWnd这个窗体上,如果识别出了结果就会产生Msg这个消息,并会emit到hWnd这个窗体

 

    if(FAILED(hr))

 

    {

 

        QMessageBox::information(NULL, "Error", "Error SetNotifyWindowMessage", MB_OK);

 

        return hr;

 

    }

 

    const ULONGLONG ullInterest = SPFEI(SPEI_SOUND_START) | SPFEI(SPEI_SOUND_END) |

 

                                      SPFEI(SPEI_PHRASE_START) | SPFEI(SPEI_RECOGNITION) |

 

                                      SPFEI(SPEI_FALSE_RECOGNITION) | SPFEI(SPEI_HYPOTHESIS) |

 

                                      SPFEI(SPEI_INTERFERENCE) | SPFEI(SPEI_RECO_OTHER_CONTEXT) |

 

                                      SPFEI(SPEI_REQUEST_UI) | SPFEI(SPEI_RECO_STATE_CHANGE) |

 

                                      SPFEI(SPEI_PROPERTY_NUM_CHANGE) | SPFEI(SPEI_PROPERTY_STRING_CHANGE);

 

    hr = m_cpRecoContext->SetInterest(ullInterest, ullInterest);   //设置感兴趣的事件

 

    if(FAILED(hr))

 

    {

 

        QMessageBox::information(NULL, "Error", "Error set interest", MB_OK);

 

    }

 

    return hr;

 

}

 


 

HRESULT SREngine::LoadCmdFromFile(QString XMLFileName)

 

{

 

    HRESULT hr = S_OK;

 


 

    if(!m_cpCmdGrammar)

 

    {

 

        hr = m_cpRecoContext ->CreateGrammar(MYGRAMMARID, &m_cpCmdGrammar);  //命令式(command and control---C&C)

 

        if(FAILED(hr))

 

        {

 

            QMessageBox::information(NULL, "Error", "Error Creategammar", MB_OK);

 

            return hr;

 

        }

 


 

        WCHAR wszXMLFile[256] = L"";

 

        XMLFileName.toWCharArray(wszXMLFile);  //ASNI TO UNICODE

 

        //LAOD RULE FROME XML FILE

 

        hr = m_cpCmdGrammar->LoadCmdFromFile(wszXMLFile, SPLO_DYNAMIC);

 

        if(FAILED(hr))

 

        {

 

            QMessageBox::information(NULL, "Error", "Error LoadCmdFromFile", MB_OK);

 

            return hr;

 

        }

 

    }

 

    return hr;

 

}

 


 

HRESULT SREngine::SetRuleState(const WCHAR * pszRuleName, const WCHAR *pszValue, BOOL fActivate)

 

{

 

    HRESULT hr = S_OK;

 

    if(fActivate)

 

    {

 

        hr = m_cpCmdGrammar ->SetRuleState(pszRuleName, NULL, SPRS_ACTIVE);

 

    }

 

    else

 

    {

 

        hr = m_cpCmdGrammar ->SetRuleState(pszRuleName, NULL, SPRS_INACTIVE);

 

    }

 

    return hr;

 

}


在MainWindow的ui上添加一个“开启”按钮,然后给它添加槽函数: 

void MainWindow:: on_pushButtonStart_clicked()

 

{

 

    HRESULT hr = m_SREngine.InitializeSapi(this->winId(), WM_RECOEVENT);  //初始化SAPI

 

    if(FAILED(hr))

 

    {

 

        return;

 

    }

 

    QString grammarFileName = "../SpeechGrammar.xml";

 

    hr = m_SREngine.LoadCmdFromFile(grammarFileName);   //创建语法规则

 

    if(FAILED(hr))

 

    {

 

        return;

 

    }

 


 

    /* 激活语音控制 */

 

    hr = m_SREngine.SetRuleState(NULL, NULL, SPRS_ACTIVE);

 

    if(FAILED(hr))

 

    {

 

        QMessageBox::information(NULL, "Error", "SetRuleState Active Error!", MB_OK);

 

        return;

 

    }

 

    setWindowTitle("Sound Start");

 

    ui->pushButtonStart->setEnabled(false);

 

}


实现的时候,一个比较棘手的问题是,如何将MFC的消息机制用QT来取代。我首先想到的当然是用信号槽机制来实现,但是这里完全跟信号槽对不上号啊,套不进去啊!!!尝试了半天,在google和baidu上搜啊搜,找到了很少比较相关的资料。没办法我就加QT技术的QQ群,问群里大牛,但可能我没描述清楚,还是解决不了。 
最后,还是google搜,通过搜QT和WinAPI混合编程,找到了一两个比较好的结果,里面有提到用winEvent来截获窗体的消息,于是看到希望了,哈哈哈。我的winEvent函数如下: 

bool MainWindow::winEvent(MSG* pMsg, long* result)

 

{

 

    setWindowTitle("Control - Debug: winEvent");

 

    if(pMsg->message == WM_RECOEVENT)

 

    {

 

        *result = this->OnRecoEvent();   //OnRecoEvent函数是具体的处理过程,其中可获取识别结果并对不同的结果做相应的处理

 

    }

 

    return false;

 

}


这里的winEvent函数就相当于WinAPI里的WinProc函数,但不需要用信号槽来显式的将信号和槽connect起来,只要有该窗体有消息产生,它就会执行。这样就将消息机制实现了,消息从SAPI的函数里产生,发送到了MainWindow窗体,再在MainWindow的winEvent函数里截获该消息进行相应的处理。 
该实例的所有源文件已上传到GitHub:https://github.com/ibillxia/Demo/tree/master/QtSAPIDemo

posted on 2013-07-25 13:36  ldjhust  阅读(4352)  评论(0编辑  收藏  举报