//WindowsのSAPI5またはMicrosoft Speech PlatformとC++で音声認識を使用するサンプル。 // //■サンプルの動作内容は以下の通り。 //1.認識エンジン名の一覧を表示し日本語認識エンジンを設定。 //2.入力デバイス名の一覧を表示し標準入力デバイスを設定。 //3.音声入力を永久ループで待ち受け、認識文を表示し続ける(非コールバック)。 //4.「終了」という文字を含む文字列が来ればループを打ち切り終了。 // //■その他雑記。 //・開発環境は「Win7x64」と「VC++2010」。 //・「Win32 コンソール アプリケーション」。 //・Unicode文字セット(SAPI5の関数群はUnicodeを使用するためマルチバイト文字セットを使う場合は変換が必要になる)。 //・「sapi.h」をプロジェクトに含める必要がある。 //・サンプルであるためSAPI5関数群の戻り値hResultのチェックとエラー処理は省略している。 // //http://denspe.blog84.fc2.com/blog-entry-190.html //古い形式の関数の使用警告を表示しないプラグマ。 #pragma warning(disable:4996) #include #include #include #include #include "sapi.h" //開始時にCoInitialize、終了時にCoUninitializeするだけのクラス。 //COMコンポーネントを使用するアプリケーションの最初で実体を確保しておくことで、終了時にCoUninitializeを不要にする。 //CComPtr等の自動解放系システムの使用時には、システムが各COMコンポーネントを自動解放した後に //CoUninitializeが実行されるようにしなければならないため、スコープを分ける必要がある。 class CAutoCoInitialize { public: HRESULT hResult; //コンストラクタ。 CAutoCoInitialize():hResult(S_FALSE){hResult=CoInitialize(NULL);} //デストラクタ。 ~CAutoCoInitialize(){CoUninitialize();hResult=S_FALSE;} } AutoCoInitialize; //COMオブジェクトの自動解放をカプセル化したもの。 //atlbase.hにあるCComQIPtrの劣化版。atlbase.hを使用しないため似たものを自作。 template class AutoCom { public: //実装していない機能は実体をそのまま使う。 T *pCom; //標準のコンストラクタ。 AutoCom(void):pCom(NULL){} //初期化用コンストラクタ(コピー・コンストラクタ呼び出し)。 AutoCom(T *pSrcCom):pCom(NULL){operator=(pSrcCom);} //デストラクタ。 ~AutoCom(void){if(pCom){pCom->Release();pCom=NULL;}} ////////////////////// //有効・無効の確認。// ////////////////////// bool IsAvailable(void){return pCom?true:false;} bool IsNull(void){return pCom?false:true;} /////////////////////////////////////////////////// //COMポインタと同等に扱えるようにするための定義。// /////////////////////////////////////////////////// operator T*(void){return pCom;} operator T**(void){return &pCom;} operator T&(void){return *pCom;} T& operator*(void){return *pCom;} T** operator&(void){if(pCom){pCom->Release();pCom=NULL;}return &pCom;} T* operator->(void){return pCom;} T* operator=(T *pSrcCom){if(pSrcCom==pCom)return pCom;if(pCom)pCom->Release();pCom=pSrcCom;return pCom;} //////////////////////////// //その他作業用関数の定義。// //////////////////////////// //COMオブジェクト生成。 HRESULT Create(GUID ClsID,GUID IID=__uuidof(T)) { if(pCom){pCom->Release();pCom=NULL;} HRESULT hResult=CoCreateInstance(ClsID,NULL,CLSCTX_ALL,IID,(LPVOID *)&pCom); if(FAILED(hResult))pCom=NULL; return hResult; } }; //メイン関数。 int _tmain(int argc,TCHAR* argv[]) { //日本語の表示に必要な処理。 _tsetlocale(LC_ALL,_T("")); //SAPI5関連の戻り値を受ける。 //今回のサンプルではただ受けるだけで内容は無視しているが本来はエラー処理等を行う必要がある。 HRESULT hResult=S_OK; ///////////////////////// //ISpRecognizerの生成。// ///////////////////////// //ISpRecognizerは認識エンジンを管理するオブジェクト。 AutoCom acRecognizer; acRecognizer.Create(CLSID_SpInprocRecognizer); ////////////////////////////////// //認識エンジンの一覧表示と設定。// ////////////////////////////////// //認識エンジンの変更は、CreateRecoContext後にはできなくなるため、ISpRecognizer生成直後に行う必要がある。 if(acRecognizer.IsAvailable()) { //認識エンジン一覧を管理するオブジェクトの生成。 AutoCom acRecognizerCategory; acRecognizerCategory.Create(CLSID_SpObjectTokenCategory); //認識エンジン一覧記録位置の設定(上はMSSP11で下がSAPI5)。 //hResult=acRecognizerCategory->SetId(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech Server\\v11.0\\Recognizers",TRUE); hResult=acRecognizerCategory->SetId(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Recognizers",TRUE); //認識エンジン一覧を格納するオブジェクト。 AutoCom acRecognizerTokens; //認識エンジン一覧の取得。 hResult=acRecognizerCategory->EnumTokens(NULL,NULL,&acRecognizerTokens); //標準認識エンジンIDの取得。 WCHAR *pDefaultRecognizerId=NULL; if(FAILED(acRecognizerCategory->GetDefaultTokenId(&pDefaultRecognizerId)))pDefaultRecognizerId=NULL; //認識エンジンの一覧表示と設定。 wprintf(L"【認識エンジン一覧】\n"); while(true) { //特定の認識エンジンを格納するオブジェクト。 AutoCom acRecognizerToken; //以下で先頭から末尾までを走査できる。 if(FAILED(acRecognizerTokens->Next(1,&acRecognizerToken,NULL))||acRecognizerToken.IsNull())break; //認識エンジンIDを取得し標準認識エンジンIDと比較する。 WCHAR *pRecognizerId=NULL; bool IsDefaultRecognizer=false; if(SUCCEEDED(acRecognizerToken->GetId(&pRecognizerId))) { //標準認識エンジンIDと一致すれば標準マーク「*」をつける指示。 if(pDefaultRecognizerId&&wcscmp(pRecognizerId,pDefaultRecognizerId)==0)IsDefaultRecognizer=true; //取得した文字列は解放する必要がある。 CoTaskMemFree(pRecognizerId);pRecognizerId=NULL; } //認識エンジン名の表示と設定。 WCHAR *pRecognizerName=NULL; //GetStringValue関数を必要な情報名を与えずに呼び出すと、標準値(ここでは認識エンジン名)が返る。 if(SUCCEEDED(acRecognizerToken->GetStringValue(NULL,&pRecognizerName))) { //標準認識エンジンであれば表示名の頭に「*」をつける。 if(IsDefaultRecognizer) wprintf(L"*%s\n",pRecognizerName); else wprintf(L" %s\n",pRecognizerName); //言語IDが411(日本語)の場合はその認識エンジンに設定。 BOOL IsMatches=FALSE; hResult=acRecognizerToken->MatchesAttributes(L"language=411",&IsMatches); if(IsMatches==TRUE)hResult=acRecognizer->SetRecognizer(acRecognizerToken); //取得した文字列は解放する必要がある。 CoTaskMemFree(pRecognizerName);pRecognizerName=NULL; } } //認識エンジンを取得することで正しく設定されたか確認。 AutoCom acCurrentRecognizerToken; WCHAR *pCurrentRecognizerName=NULL; if(SUCCEEDED(acRecognizer->GetRecognizer(&acCurrentRecognizerToken))) { //取得に成功すれば設定された認識エンジン名を取得。 //GetStringValue関数を必要な情報名を与えずに呼び出すと、標準値(ここでは認識エンジン名)が返る。 hResult=acCurrentRecognizerToken->GetStringValue(NULL,&pCurrentRecognizerName); } else if(pDefaultRecognizerId) { //取得に失敗した(認識エンジンが設定されていない)場合は標準認識エンジンを設定。 acCurrentRecognizerToken.Create(CLSID_SpObjectToken); //標準認識エンジンを取得。 acCurrentRecognizerToken->SetId(NULL,pDefaultRecognizerId,FALSE); //取得した標準認識エンジンを認識エンジンとして設定し認識エンジン名を取得。 if(SUCCEEDED(acRecognizer->SetRecognizer(acCurrentRecognizerToken)))hResult=acCurrentRecognizerToken->GetStringValue(NULL,&pCurrentRecognizerName); } //使用する認識エンジン名の表示。 if(pCurrentRecognizerName) { wprintf(L"※%sを使用。\n\n",pCurrentRecognizerName); //取得した文字列は解放する必要がある。 CoTaskMemFree(pCurrentRecognizerName);pCurrentRecognizerName=NULL; } else { //使用する認識エンジン名が設定されていない場合は非表示でカラ改行。 wprintf(L"\n"); } //取得した文字列は解放する必要がある。 if(pDefaultRecognizerId){CoTaskMemFree(pDefaultRecognizerId);pDefaultRecognizerId=NULL;} } ////////////////////////////////// //入力デバイスの一覧表示と設定。// ////////////////////////////////// //InProc(非共有)タイプの音声認識システムは標準では入力デバイスが未設定であるため、必ず何かを設定する必要がある。 if(acRecognizer.IsAvailable()) { //入力デバイス一覧を管理するオブジェクトの生成。 AutoCom acAudioInputCategory; acAudioInputCategory.Create(CLSID_SpObjectTokenCategory); //入力デバイス一覧記録位置の設定(上はMSSP11で下がSAPI5)。 //hResult=acAudioInputCategory->SetId(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech Server\\v11.0\\AudioInput",TRUE); hResult=acAudioInputCategory->SetId(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\AudioInput",TRUE); //入力デバイス一覧を格納するオブジェクト。 AutoCom acAudioInputTokens; //入力デバイス一覧の取得。 hResult=acAudioInputCategory->EnumTokens(NULL,NULL,&acAudioInputTokens); //標準入力デバイスIDの取得。 WCHAR *pDefaultAudioInputId=NULL; if(FAILED(acAudioInputCategory->GetDefaultTokenId(&pDefaultAudioInputId)))pDefaultAudioInputId=NULL; //入力デバイスの一覧表示と設定。 wprintf(L"【入力デバイス一覧】\n"); while(true) { //特定の入力デバイスを格納するオブジェクト。 AutoCom acAudioInputToken; //以下で先頭から末尾までを走査できる。 if(FAILED(acAudioInputTokens->Next(1,&acAudioInputToken,NULL))||acAudioInputToken.IsNull())break; //入力デバイスIDを取得し標準入力デバイスIDと比較する。 WCHAR *pAudioInputId=NULL; bool IsDefaultAudioInput=false; if(SUCCEEDED(acAudioInputToken->GetId(&pAudioInputId))) { //標準入力デバイスIDと一致すれば標準マーク「*」をつける指示。 if(pDefaultAudioInputId&&wcscmp(pAudioInputId,pDefaultAudioInputId)==0)IsDefaultAudioInput=true; //取得した文字列は解放する必要がある。 CoTaskMemFree(pAudioInputId);pAudioInputId=NULL; } //入力デバイス名の表示と設定。 WCHAR *pAudioInputName=NULL; //GetStringValue関数を必要な情報名を与えずに呼び出すと、標準値(ここでは入力デバイス名)が返る。 if(SUCCEEDED(acAudioInputToken->GetStringValue(NULL,&pAudioInputName))) { //標準入力デバイスであれば表示名の頭に「*」をつける。 if(IsDefaultAudioInput) { //標準入力デバイスであれば表示名の頭に「*」をつける。 wprintf(L"*%s\n",pAudioInputName); //標準入力デバイスであればその入力デバイスに設定。 hResult=acRecognizer->SetInput(acAudioInputToken,TRUE); } else { //標準入力デバイスでなければ表示名の頭には半角スペースを置く。 wprintf(L" %s\n",pAudioInputName); } //取得した文字列は解放する必要がある。 CoTaskMemFree(pAudioInputName);pAudioInputName=NULL; } } //入力デバイスを取得することで正しく設定されたか確認。 AutoCom acCurrentAudioInputToken; WCHAR *pCurrentAudioInputName=NULL; if(SUCCEEDED(acRecognizer->GetInputObjectToken(&acCurrentAudioInputToken))) { //取得に成功すれば設定された入力デバイス名を取得。 //GetStringValue関数を必要な情報名を与えずに呼び出すと、標準値(ここでは入力デバイス名)が返る。 hResult=acCurrentAudioInputToken->GetStringValue(NULL,&pCurrentAudioInputName); } else if(pDefaultAudioInputId) { //取得に失敗した(入力デバイスが設定されていない)場合は標準入力デバイスを設定。 acCurrentAudioInputToken.Create(CLSID_SpObjectToken); //標準入力デバイスを取得。 acCurrentAudioInputToken->SetId(NULL,pDefaultAudioInputId,FALSE); //取得した標準入力デバイスを入力デバイスとして設定し入力デバイス名を取得。 if(SUCCEEDED(acRecognizer->SetInput(acCurrentAudioInputToken,TRUE)))hResult=acCurrentAudioInputToken->GetStringValue(NULL,&pCurrentAudioInputName); } //使用する入力デバイス名の表示。 if(pCurrentAudioInputName) { wprintf(L"※%sを使用。\n\n",pCurrentAudioInputName); //取得した文字列は解放する必要がある。 CoTaskMemFree(pCurrentAudioInputName);pCurrentAudioInputName=NULL; } else { //使用する入力デバイス名が設定されていない場合は非表示でカラ改行。 wprintf(L"\n"); } //取得した文字列は解放する必要がある。 if(pDefaultAudioInputId){CoTaskMemFree(pDefaultAudioInputId);pDefaultAudioInputId=NULL;} } //////////////////////////////// //ISpRecoContextの生成と設定。// //////////////////////////////// //ISpRecoContextは音声認識管理用オブジェクト。 //認識エンジン管理オブジェクトISpRecognizeや後述の文法管理オブジェクトISpRecoGrammarを統括し //音声認識利用者はこのISpRecoContextオブジェクトを通して認識結果を取得する。 //認識エンジン管理オブジェクトISpRecognizeと密接に結びついており、このオブジェクトが単体で存在することはない。 //このオブジェクトを直接生成した場合には内部で標準の認識エンジンを管理するISpRecognizeが生成される。 //また、一度このオブジェクトが生成されると、 //それが持つISpRecognizeが管理する認識エンジンやISpRecognize自体を差し替えることはできなくなるため、 //認識言語(エンジン)を変更する際にはこのオブジェクトも作り直す必要がある。 AutoCom acRecoContext; acRecognizer->CreateRecoContext(&acRecoContext); //認識イベント発生まで待機するための関数「WaitForNotifyEvent()」を使用するための準備。 //なぜかコメントアウトしても問題なく動作したが一応やっておく。 hResult=acRecoContext->SetNotifyWin32Event(); //どのイベントが発生した場合に通知させるかを設定。「SPEI_RECOGNITION」は認識完了時。 hResult=acRecoContext->SetInterest(SPFEI(SPEI_RECOGNITION),SPFEI(SPEI_RECOGNITION)); //////////////////////////////// //ISpRecoGrammarの生成と設定。// //////////////////////////////// //ISpRecoGrammarは音声認識文法管理用オブジェクト。 //あらかじめ登録した単語(文章)のどれが一番近いか?を認識する「コマンド認識」モードの場合は //ここでこのオブジェクトに対してコマンドの登録などを行う。 //自由に話した内容を文字列化する「口述筆記」モード使用時には特にやることはない。 AutoCom acRecoGrammar; acRecoContext->CreateGrammar(NULL,&acRecoGrammar); //辞書の読み込み。読み込み対象無指定(NULL)で一般的な辞書(「口述筆記」モード用の辞書)が読み込まれる。 hResult=acRecoGrammar->LoadDictation(NULL,SPLO_STATIC); //読み込んだ辞書の活動を開始させる。これは実質的には認識処理開始の合図となる。 hResult=acRecoGrammar->SetDictationState(SPRS_ACTIVE); //////////////////// //イベントの処理。// //////////////////// //永久ループでイベント発生を待受け続け、日本語で「終了」の文字が含まれる文が来るまで認識結果を表示し続ける。 bool IsLoop=true; while(IsLoop) { //イベント発生まで待機。 hResult=acRecoContext->WaitForNotifyEvent(INFINITE); //イベントが発生すれば発生したイベントを取得。 SPEVENT SpEvent;memset(&SpEvent,0,sizeof(SPEVENT)); hResult=acRecoContext->GetEvents(1,&SpEvent,NULL); //発生したイベントの種類を確認し必要な処理を実行する。 switch(SpEvent.eEventId) { case SPEI_RECOGNITION: //「SpEvent.elParamType」が「SPET_LPARAM_IS_OBJECT」のとき「SpEvent.lParam」に結果オブジェクト(ISpRecoResult)が入っている。 if(SpEvent.elParamType==SPET_LPARAM_IS_OBJECT) { //「SpEvent.lParam」を結果オブジェクトISpRecoResultとして扱う。 ISpRecoResult* pResult=(ISpRecoResult *)SpEvent.lParam; //ISpRecoResultのGetText関数で認識結果を文字列で取得できる。 WCHAR *pResultText=NULL; if(SUCCEEDED(pResult->GetText(SP_GETWHOLEPHRASE,SP_GETWHOLEPHRASE,TRUE,&pResultText,NULL))) { wprintf(L"%s\n",pResultText); //「終了」の文字が含まれればループ打ち切り。 if(wcsstr(pResultText,L"終了"))IsLoop=false; //取得した文字列は解放する必要がある。 CoTaskMemFree(pResultText);pResultText=NULL; } } break; default: break; } //イベント通知のためにSAPI5側が内部で動的確保した各種情報は受け取り側の責任で解放する必要がある。 //これは「sphelper.h」内で定義されているヘルパー関数SpClearEventで行えるが //「sphelper.h」はATLがない環境では使用できないため、必要な処理を手作業で移植する。 //以下「sphelper.h」のSpClearEvent関数の内容を移植。 if(SpEvent.elParamType!=SPEI_UNDEFINED) { if(SpEvent.elParamType==SPET_LPARAM_IS_POINTER||SpEvent.elParamType==SPET_LPARAM_IS_STRING) CoTaskMemFree((void *)SpEvent.lParam); else if(SpEvent.elParamType==SPET_LPARAM_IS_TOKEN||SpEvent.elParamType==SPET_LPARAM_IS_OBJECT) ((IUnknown*)SpEvent.lParam)->Release(); } memset(&SpEvent,0,sizeof(SPEVENT)); //以上「sphelper.h」のSpClearEvent関数の内容を移植。 } ////////// //終了。// ////////// //認識結果に「終了」の文字が含まれていれば認識ループが打ち切られてここに来る。 return EXIT_SUCCESS; }