From a45d4b2d7db0f191ef54e8b7aef1eccf3d5c7534 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 22 Oct 2014 10:17:29 -0700 Subject: [PATCH] Add first pass at Windows speech recognizer implementation --- interface/src/SpeechRecognizer.cpp | 135 +++++++++++++++++++++++++++-- interface/src/SpeechRecognizer.h | 21 +++++ 2 files changed, 151 insertions(+), 5 deletions(-) diff --git a/interface/src/SpeechRecognizer.cpp b/interface/src/SpeechRecognizer.cpp index 02f3ece866..2faf4a1033 100644 --- a/interface/src/SpeechRecognizer.cpp +++ b/interface/src/SpeechRecognizer.cpp @@ -10,20 +10,39 @@ // #include - -#ifdef Q_OS_WIN +#include #include "SpeechRecognizer.h" +#ifdef Q_OS_WIN + SpeechRecognizer::SpeechRecognizer() : QObject(), _enabled(false), - _commands() { + _commands(), + _comInitialized(false), + _speechRecognizer(NULL), + _speechRecognizerContext(NULL), + _speechRecognizerGrammar(NULL), + _commandRecognizedEvent(NULL), + _commandRecognizedNotifier(NULL) { + HRESULT hr = ::CoInitialize(NULL); + + if (SUCCEEDED(hr)) { + _comInitialized = true; + } } SpeechRecognizer::~SpeechRecognizer() { + if (_comInitialized) { + ::CoUninitialize(); + } + + if (_speechRecognizerGrammar) { + _speechRecognizerGrammar.Release(); + } } void SpeechRecognizer::handleCommandRecognized(const char* command) { @@ -31,15 +50,53 @@ void SpeechRecognizer::handleCommandRecognized(const char* command) { } void SpeechRecognizer::setEnabled(bool enabled) { - if (enabled == _enabled) { + if (enabled == _enabled || !_comInitialized) { return; } _enabled = enabled; + if (_enabled) { - } else { + HRESULT hr = S_OK; + // Create a shared recognizer. + if (SUCCEEDED(hr)) { + hr = _speechRecognizerContext.CoCreateInstance(CLSID_SpSharedRecoContext); + } + + // Set up event notification mechanism. + if (SUCCEEDED(hr)) { + hr = _speechRecognizerContext->SetNotifyWin32Event(); + } + if (SUCCEEDED(hr)) { + _commandRecognizedEvent = _speechRecognizerContext->GetNotifyEventHandle(); + if (_commandRecognizedEvent == NULL) { + hr = S_FALSE; + } + } + if (SUCCEEDED(hr)) { + _commandRecognizedNotifier = new QWinEventNotifier(_commandRecognizedEvent); + connect(_commandRecognizedNotifier, SIGNAL(activated(HANDLE)), SLOT(notifyCommandRecognized(HANDLE))); + } + + // Set which events to be notified of. + if (SUCCEEDED(hr)) { + hr = _speechRecognizerContext->SetInterest(SPFEI(SPEI_RECOGNITION), SPFEI(SPEI_RECOGNITION)); + } + + // Create grammar and load commands. + if (SUCCEEDED(hr)) { + hr = _speechRecognizerContext->CreateGrammar(NULL, &_speechRecognizerGrammar); + } + if (SUCCEEDED(hr)) { + reloadCommands(); + } + + _enabled = SUCCEEDED(hr); + + } else { + _speechRecognizerContext.Release(); } emit enabledUpdated(_enabled); @@ -56,7 +113,75 @@ void SpeechRecognizer::removeCommand(const QString& command) { } void SpeechRecognizer::reloadCommands() { + if (!_enabled) { + return; + } + HRESULT hr = S_OK; + + if (SUCCEEDED(hr)) { + hr = _speechRecognizerContext->Pause(NULL); + } + + if (SUCCEEDED(hr)) { + WORD langId = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL); + hr = _speechRecognizerGrammar->ResetGrammar(langId); + } + + DWORD ruleID = 0; + SPSTATEHANDLE initialState; + for (QSet::const_iterator iter = _commands.constBegin(); iter != _commands.constEnd(); iter++) { + ruleID += 1; + + if (SUCCEEDED(hr)) { + hr = _speechRecognizerGrammar->GetRule(NULL, ruleID, SPRAF_TopLevel | SPRAF_Active | SPRAF_Dynamic, TRUE, &initialState); + } + + if (SUCCEEDED(hr)) { + const std::wstring command = (*iter).toStdWString(); // DJRTODO: Better, shorter name + hr = _speechRecognizerGrammar->AddWordTransition(initialState, NULL, command.c_str(), L" ", SPWT_LEXICAL, 1.0, NULL); + } + } + + if (SUCCEEDED(hr)) { + hr = _speechRecognizerGrammar->Commit(0); + } + + if (SUCCEEDED(hr)) { + hr = _speechRecognizerContext->Resume(NULL); + } + + if (SUCCEEDED(hr)) { + hr = _speechRecognizerGrammar->SetRuleState(NULL, NULL, SPRS_ACTIVE); + } + + if (FAILED(hr)) { + qDebug() << "ERROR: Didn't successfully reload commands"; + } +} + +void SpeechRecognizer::notifyCommandRecognized(HANDLE handle) { + + SPEVENT eventItem; + memset(&eventItem, 0, sizeof(SPEVENT)); + HRESULT hr = _speechRecognizerContext->GetEvents(1, &eventItem, NULL); + + if (SUCCEEDED(hr)) { + if (eventItem.eEventId == SPEI_RECOGNITION && eventItem.elParamType == SPET_LPARAM_IS_OBJECT) { + ISpRecoResult* recognitionResult = reinterpret_cast(eventItem.lParam); + wchar_t* pText; + + hr = recognitionResult->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, FALSE, &pText, NULL); + + if (SUCCEEDED(hr)) { + QString text = QString::fromWCharArray(pText); + handleCommandRecognized(text.toStdString().c_str()); + ::CoTaskMemFree(pText); + } + + recognitionResult->Release(); + } + } } #endif // Q_OS_WIN \ No newline at end of file diff --git a/interface/src/SpeechRecognizer.h b/interface/src/SpeechRecognizer.h index edd4abe1d6..b343326de3 100644 --- a/interface/src/SpeechRecognizer.h +++ b/interface/src/SpeechRecognizer.h @@ -16,6 +16,13 @@ #include #include +#ifdef Q_OS_WIN +#include + +#include +#include +#endif + class SpeechRecognizer : public QObject { Q_OBJECT public: @@ -40,8 +47,22 @@ protected: private: bool _enabled; QSet _commands; +#if defined(Q_OS_MAC) void* _speechRecognizerDelegate; void* _speechRecognizer; +#elif defined(Q_OS_WIN) + bool _comInitialized; + CComPtr _speechRecognizer; + CComPtr _speechRecognizerContext; + CComPtr _speechRecognizerGrammar; + HANDLE _commandRecognizedEvent; + QWinEventNotifier* _commandRecognizedNotifier; +#endif + +#if defined(Q_OS_WIN) +private slots: + void notifyCommandRecognized(HANDLE handle); +#endif }; #endif // hifi_SpeechRecognizer_h