1
votes

I just recently downloaded Visual Studio 2015 (Community) so that I could develop an application using Microsoft SAPI TTS. I've installed the 5.1 SDK and then subsequently the 5.4 SDK for Speech, and have installed the Runtime Languages for SAPI.

So far, I can manage to get the app to work by using the phrases I want SAPI to speak by both direct arguments and from reading a SSML .XML file. All the features of SAPI and SSML work except for anything to do with changing the voice token. (e.g., <voice xml:lang="pl-PL"> ... <voice required="Gender:Female"> ... etc)

I read a few forums on how to set the language / voice token, and I have tried the following code:

if(FAILED(::CoInitialized(NULL))
  return false;

HRESULT                         hr = S_OK;
CComPtr<ISpVoice>               cpVoice;
CComPtr<ISpObjectTokenCategory> cpObjectCat;
CComPtr<ISpObjectToken>         cpObjectToken;
CComPtr<IEnumSpObjectTokens>    cpEnum;

hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&cpVoice);

if(SUCCEEDED(hr))
{
    hr = SpGetDefaultTokenFromCategoryId(SPCAT_VOICES, &cpObjectToken);
} 
if(SUCCEEDED(hr))
{
    hr = SpGetCategoryFromId(SPCAT_VOICES, &cpObjectCat);
}
if(SUCCEEDED(hr))
{
    hr = cpObjectCat->EnumTokens(NULL, NULL, &cpEnum);
}
if(SUCCEEDED(hr))
{
    hr = cpEnum->Next(1, &cpObjectToken, NULL);
    //Currently only concerned with making 1 token assign without
    //throwing exception
}
if(SUCCEEDED(hr))
{
    hr = cpVoice->SetVoice(cpObjectToken);
}
if(SUCCEEDED(hr))
{
    hr = cpVoice->Speak(L"Hello There!", NULL, NULL);
}
//... lots of commented-out code here ...
cpVoice.Release();
::CoUninitialize();
return true; // <-- Throws Exception Here

The program builds without errors, but throws error 0xC0000005 (Memory Access Violation) at the final return statement.

I did have to modify header file <sphelper.h> because of deprecated method ::GetVersionExW() ... I managed to make it work using the methods from this link: http://www.codeproject.com/Articles/678606/Part-Overcoming-Windows-s-deprecation-of-GetVe . Miraculously (and with a little tweaking of system source code, probably a bad idea), it worked.

I don't know why the program is throwing at the very end, since the problem must be in the program accessing the registry tokens. I know the typically the problem lies in pointers, so what is it that I need to do to make this work?

Note that I'm trying to make the cpVoice object use a Polish token, 'Paulina'. Is there a way to manually assign the registry token value to the object somehow?

1

1 Answers

0
votes

This is how you go through each of your installed voices. pszCurTokenId will be the description of the voice you've gotten to. You could probably print it to the console or something, or just look at the value in your debugger.

You shouldn't have to edit sphelper.h to select a voice properly. Sometimes SAPI can take a few seconds to change a voice, so I'd be patient if it feels like it's hanging. I just ran the following code on windows 7, and I verified it works for that platform.

HRESULT hr = S_OK;
CComPtr<ISpObjectToken> cpVoiceToken;
CComPtr<ISpVoice> cpVoice;

::CoInitialize(NULL);
if(SUCCEEDED(hr))
    hr = cpVoice.CoCreateInstance(CLSID_SpVoice);

ULONG ulCount = 0;
CComPtr<IEnumSpObjectTokens> cpEnum;

if(SUCCEEDED(hr))
    hr = SpEnumTokens(SPCAT_VOICES, NULL, NULL, &cpEnum);

//Get the number of voices
if(SUCCEEDED(hr))
    hr = cpEnum->GetCount(&ulCount);

for(ULONG i = 0; i < ulCount; ++i) {
    CSpDynamicString* szDescription;
    CComPtr<ISpObjectToken> cpTempVoiceToken;
    cpEnum->Item(i, &cpTempVoiceToken);
    WCHAR* pszCurTokenId = NULL;
    SpGetDescription(cpTempVoiceToken, &pszCurTokenId);
    cpVoice->SetVoice(cpTempVoiceToken);
    cpVoice->Speak(L"This is a test phrase.", SPF_DEFAULT, NULL);
    cpTempVoiceToken.Release();
}

cpVoice.Release();
cpEnum.Release();
::CoUninitialize();