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:

  1. // Get the object token for the default shared recognizer engine.
  2. HRESULT SpGetDefaultSharedRecognizerToken(ISpObjectToken **ppSharedRecognizerToken)
  3. {
  4.     HRESULT hr = S_OK;
  5.     const WCHAR pszWindowsCompatAtt[] = L"windowsV6compatible";
  6.     LANGID UIlangID = GetUserDefaultUILanguage();
  7.     
  8.     // Find recognizers that match windowsV6compatible and the primary language id of the UI.
  9.     // Use primary language because there may be several recognizers available for different sub-languages,
  10.     // and there is generally only one UI language per primary language.
  11.     // e.g. US and UK English recognizers on a US English system.
  12.     // If so, to distinguish these the locale is used below.
  13.  
  14.     CComPtr<IEnumSpObjectTokens> cpEnum;
  15.     hr = SpEnumTokensMatchingPrimaryLangID(SPCAT_RECOGNIZERS, PRIMARYLANGID(UIlangID), pszWindowsCompatAtt, &cpEnum);
  16.  
  17.     ULONG ulRecognizers(0);
  18.     if (SUCCEEDED(hr))
  19.     {
  20.         hr = cpEnum->GetCount(&ulRecognizers);
  21.     }
  22.  
  23.     if (SUCCEEDED(hr))
  24.     {
  25.         if (ulRecognizers == 0)
  26.         {
  27.             // If zero - error.
  28.             hr = SPERR_RECOGNIZER_NOT_FOUND;
  29.         }
  30.         else if (ulRecognizers == 1)
  31.         {
  32.             // If one - we are done.
  33.             hr = cpEnum->Item(0, ppSharedRecognizerToken);
  34.         }
  35.         else
  36.         {
  37.             // More than one recognizer matches - we must pick the best default:
  38.             BOOL fFoundDefault = FALSE;
  39.  
  40.             // If there's no language-specific default then use the locale.
  41.             LANGID localeID = GetUserDefaultLangID();
  42.             if (!fFoundDefault && SUCCEEDED(hr) &&
  43.                 PRIMARYLANGID(UIlangID) == PRIMARYLANGID(localeID))
  44.             {
  45.                 // First see if there's an engine whose language exactly matches the locale.
  46.                 // For example if the langid is U.S English, but the locale is U.K. English
  47.                 // and there's a U.K. recognizer then use that.
  48.  
  49.                 hr = S_OK;
  50.                 WCHAR pszLocaleString[] = L"Language=XXXXXXXX";
  51.                 SpHexFromUlong(pszLocaleString + wcslen(pszLocaleString) - 8, localeID);
  52.                 for (ULONG ul = 0; SUCCEEDED(hr) && ul < ulRecognizers; ul++)
  53.                 {
  54.                     CComPtr<ISpObjectToken> cpToken;
  55.                     hr = cpEnum->Item(ul, &cpToken);
  56.                     if (SUCCEEDED(hr))
  57.                     {
  58.                         hr = cpToken->MatchesAttributes(pszLocaleString, &fFoundDefault);
  59.                     }
  60.  
  61.                     if (SUCCEEDED(hr) && fFoundDefault)
  62.                     {
  63.                         *ppSharedRecognizerToken = cpToken.Detach();
  64.                         break;
  65.                     }
  66.                 }
  67.             }
  68.  
  69.             // If there's no engine that directly matches the locale see if the backup list of SupportedLocales is matched.
  70.             if (!fFoundDefault && (SUCCEEDED(hr) || hr == SPERR_NOT_FOUND) &&
  71.                 PRIMARYLANGID(UIlangID) == PRIMARYLANGID(localeID))
  72.             {
  73.                 // See if there's an engine which has a supported Locale matches the current locale.
  74.                 // For example if the langid is U.S English, but the locale is Australian English
  75.                 // there may be no recognizer that directly supports Australian English,
  76.                 // but a U.K. recognizer might specify it can be used in the Australian English locale with this attribute.
  77.  
  78.                 hr = S_OK;
  79.                 WCHAR pszSupportedLocalesString[] = L"SupportedLocales=XXXXXXXX";
  80.                 SpHexFromUlong(pszSupportedLocalesString + wcslen(pszSupportedLocalesString) - 8, localeID);
  81.                 for (ULONG ul = 0; SUCCEEDED(hr) && ul < ulRecognizers; ul++)
  82.                 {
  83.                     CComPtr<ISpObjectToken> cpToken;
  84.                     hr = cpEnum->Item(ul, &cpToken);
  85.                     if (SUCCEEDED(hr))
  86.                     {
  87.                         hr = cpToken->MatchesAttributes(pszSupportedLocalesString, &fFoundDefault);
  88.                     }
  89.  
  90.                     if (SUCCEEDED(hr) && fFoundDefault)
  91.                     {
  92.                         *ppSharedRecognizerToken = cpToken.Detach();
  93.                         break;
  94.                     }
  95.                 }
  96.             }
  97.  
  98.             // We still haven't found a match - just pick the first recognizer.
  99.             if (!fFoundDefault && (SUCCEEDED(hr) || hr == SPERR_NOT_FOUND))
  100.             {
  101.                 CComPtr<ISpObjectToken> cpToken;
  102.                 hr = cpEnum->Item(0, ppSharedRecognizerToken);
  103.             }
  104.         }
  105.     }
  106.  
  107.     return hr;
  108. }
  109.  
  110.  
  111. // Return a token enumerator containing all tokens that match the primary language
  112. // of a particular language id. pszRequiredAttributes can be used to specify additional attributes all tokens must have.
  113. HRESULT SpEnumTokensMatchingPrimaryLangID(const LPCWSTR pszCategoryId, LANGID priLangID, LPCWSTR pszRequiredAtts,
  114.                                                  IEnumSpObjectTokens **ppEnum)
  115. {
  116.     HRESULT hr = S_OK;
  117.  
  118.     // First enumerate the tokens using pszRequiredAtts.
  119.     CComPtr<ISpObjectTokenCategory> cpCategory;
  120.     hr = SpGetCategoryFromId(pszCategoryId, &cpCategory);
  121.     
  122.     CComPtr<IEnumSpObjectTokens> cpEnum;
  123.     if (SUCCEEDED(hr))
  124.     {
  125.         hr = cpCategory->EnumTokens(pszRequiredAtts, NULL, &cpEnum);
  126.     }
  127.  
  128.     ULONG ulTokens(0);
  129.     if (SUCCEEDED(hr))
  130.     {
  131.         hr = cpEnum->GetCount(&ulTokens);
  132.     }
  133.  
  134.     // Create enumerator to store new tokens.
  135.     CComPtr<ISpObjectTokenEnumBuilder> cpBuilder;
  136.     if (SUCCEEDED(hr))
  137.     {
  138.         hr = cpBuilder.CoCreateInstance(CLSID_SpObjectTokenEnum);
  139.     }
  140.     if (SUCCEEDED(hr))
  141.     {
  142.         hr = cpBuilder->SetAttribs(NULL, NULL);
  143.     }
  144.  
  145.     // Now, for each token, check language string to see if it matches.
  146.     for (ULONG ul = 0; SUCCEEDED(hr) && ul < ulTokens; ul++)
  147.     {
  148.         LANGID tokenLangID(0);
  149.         CComPtr<ISpObjectToken> cpToken;
  150.         hr = cpEnum->Item(ul, &cpToken);
  151.         if (SUCCEEDED(hr))
  152.         {
  153.             // Just look at the first language id
  154.             hr = SpGetLanguageFromToken(cpToken, &tokenLangID);
  155.         }
  156.  
  157.         if (SUCCEEDED(hr) && PRIMARYLANGID(tokenLangID) == PRIMARYLANGID(priLangID))
  158.         {
  159.             // Add to builder
  160.             hr = cpBuilder->AddTokens(1, &(cpToken.p));
  161.         }
  162.     }
  163.  
  164.     if (SUCCEEDED(hr))
  165.     {
  166.         hr = cpBuilder->Reset();
  167.     }
  168.     if (SUCCEEDED(hr))
  169.     {
  170.         *ppEnum = cpBuilder.Detach();
  171.     }
  172.  
  173.     return hr;
  174. }