Specifying parameters for an inproc Recognizer
I’ve seen a couple of questions come by the listen alias about using an in-process speech recognizer. When you use an in-process recognizer, you have to specify a lot more than you need to specify with the shared recognizer.
In particular, you need to specify an audio source and a recognition engine. The shared recognizer uses the defaults for both of these.
Most people figure out that they need to specify an audio source; more commonly (and I’ve tripped over this myself) they forget to specify a recognition engine.
And while there’s a nifty helper for the audio input in sphelper.h (namely, SpGetDefaultTokenFromCategoryId(SPCAT_AUDIOIN, &cpToken)), there isn’t a helper for the recognition engine.
So, here’s a helper function that returns the object token for the default shared recognizer:
- // Get the object token for the default shared recognizer engine.
- HRESULT SpGetDefaultSharedRecognizerToken(ISpObjectToken **ppSharedRecognizerToken)
- {
- HRESULT hr = S_OK;
- const WCHAR pszWindowsCompatAtt[] = L"windowsV6compatible";
- LANGID UIlangID = GetUserDefaultUILanguage();
- // Find recognizers that match windowsV6compatible and the primary language id of the UI.
- // Use primary language because there may be several recognizers available for different sub-languages,
- // and there is generally only one UI language per primary language.
- // e.g. US and UK English recognizers on a US English system.
- // If so, to distinguish these the locale is used below.
- CComPtr<IEnumSpObjectTokens> cpEnum;
- hr = SpEnumTokensMatchingPrimaryLangID(SPCAT_RECOGNIZERS, PRIMARYLANGID(UIlangID), pszWindowsCompatAtt, &cpEnum);
- ULONG ulRecognizers(0);
- if (SUCCEEDED(hr))
- {
- hr = cpEnum->GetCount(&ulRecognizers);
- }
- if (SUCCEEDED(hr))
- {
- if (ulRecognizers == 0)
- {
- // If zero - error.
- hr = SPERR_RECOGNIZER_NOT_FOUND;
- }
- else if (ulRecognizers == 1)
- {
- // If one - we are done.
- hr = cpEnum->Item(0, ppSharedRecognizerToken);
- }
- else
- {
- // More than one recognizer matches - we must pick the best default:
- BOOL fFoundDefault = FALSE;
- // If there's no language-specific default then use the locale.
- LANGID localeID = GetUserDefaultLangID();
- if (!fFoundDefault && SUCCEEDED(hr) &&
- PRIMARYLANGID(UIlangID) == PRIMARYLANGID(localeID))
- {
- // First see if there's an engine whose language exactly matches the locale.
- // For example if the langid is U.S English, but the locale is U.K. English
- // and there's a U.K. recognizer then use that.
- hr = S_OK;
- WCHAR pszLocaleString[] = L"Language=XXXXXXXX";
- SpHexFromUlong(pszLocaleString + wcslen(pszLocaleString) - 8, localeID);
- for (ULONG ul = 0; SUCCEEDED(hr) && ul < ulRecognizers; ul++)
- {
- CComPtr<ISpObjectToken> cpToken;
- hr = cpEnum->Item(ul, &cpToken);
- if (SUCCEEDED(hr))
- {
- hr = cpToken->MatchesAttributes(pszLocaleString, &fFoundDefault);
- }
- if (SUCCEEDED(hr) && fFoundDefault)
- {
- *ppSharedRecognizerToken = cpToken.Detach();
- break;
- }
- }
- }
- // If there's no engine that directly matches the locale see if the backup list of SupportedLocales is matched.
- if (!fFoundDefault && (SUCCEEDED(hr) || hr == SPERR_NOT_FOUND) &&
- PRIMARYLANGID(UIlangID) == PRIMARYLANGID(localeID))
- {
- // See if there's an engine which has a supported Locale matches the current locale.
- // For example if the langid is U.S English, but the locale is Australian English
- // there may be no recognizer that directly supports Australian English,
- // but a U.K. recognizer might specify it can be used in the Australian English locale with this attribute.
- hr = S_OK;
- WCHAR pszSupportedLocalesString[] = L"SupportedLocales=XXXXXXXX";
- SpHexFromUlong(pszSupportedLocalesString + wcslen(pszSupportedLocalesString) - 8, localeID);
- for (ULONG ul = 0; SUCCEEDED(hr) && ul < ulRecognizers; ul++)
- {
- CComPtr<ISpObjectToken> cpToken;
- hr = cpEnum->Item(ul, &cpToken);
- if (SUCCEEDED(hr))
- {
- hr = cpToken->MatchesAttributes(pszSupportedLocalesString, &fFoundDefault);
- }
- if (SUCCEEDED(hr) && fFoundDefault)
- {
- *ppSharedRecognizerToken = cpToken.Detach();
- break;
- }
- }
- }
- // We still haven't found a match - just pick the first recognizer.
- if (!fFoundDefault && (SUCCEEDED(hr) || hr == SPERR_NOT_FOUND))
- {
- CComPtr<ISpObjectToken> cpToken;
- hr = cpEnum->Item(0, ppSharedRecognizerToken);
- }
- }
- }
- return hr;
- }
- // Return a token enumerator containing all tokens that match the primary language
- // of a particular language id. pszRequiredAttributes can be used to specify additional attributes all tokens must have.
- HRESULT SpEnumTokensMatchingPrimaryLangID(const LPCWSTR pszCategoryId, LANGID priLangID, LPCWSTR pszRequiredAtts,
- IEnumSpObjectTokens **ppEnum)
- {
- HRESULT hr = S_OK;
- // First enumerate the tokens using pszRequiredAtts.
- CComPtr<ISpObjectTokenCategory> cpCategory;
- hr = SpGetCategoryFromId(pszCategoryId, &cpCategory);
- CComPtr<IEnumSpObjectTokens> cpEnum;
- if (SUCCEEDED(hr))
- {
- hr = cpCategory->EnumTokens(pszRequiredAtts, NULL, &cpEnum);
- }
- ULONG ulTokens(0);
- if (SUCCEEDED(hr))
- {
- hr = cpEnum->GetCount(&ulTokens);
- }
- // Create enumerator to store new tokens.
- CComPtr<ISpObjectTokenEnumBuilder> cpBuilder;
- if (SUCCEEDED(hr))
- {
- hr = cpBuilder.CoCreateInstance(CLSID_SpObjectTokenEnum);
- }
- if (SUCCEEDED(hr))
- {
- hr = cpBuilder->SetAttribs(NULL, NULL);
- }
- // Now, for each token, check language string to see if it matches.
- for (ULONG ul = 0; SUCCEEDED(hr) && ul < ulTokens; ul++)
- {
- LANGID tokenLangID(0);
- CComPtr<ISpObjectToken> cpToken;
- hr = cpEnum->Item(ul, &cpToken);
- if (SUCCEEDED(hr))
- {
- // Just look at the first language id
- hr = SpGetLanguageFromToken(cpToken, &tokenLangID);
- }
- if (SUCCEEDED(hr) && PRIMARYLANGID(tokenLangID) == PRIMARYLANGID(priLangID))
- {
- // Add to builder
- hr = cpBuilder->AddTokens(1, &(cpToken.p));
- }
- }
- if (SUCCEEDED(hr))
- {
- hr = cpBuilder->Reset();
- }
- if (SUCCEEDED(hr))
- {
- *ppEnum = cpBuilder.Detach();
- }
- return hr;
- }