Windows Speech SAPI:如何列出语音的属性?

问题描述

我已使用此 Stack Overflow Answer 遍历所有已安装的 Windows 文本到语音转换语音,但我很难提取每个语音的属性。例如性别、语言、姓名等

我假设有一种方法可以提取可用于查找声音的属性,例如gender=female;language=409

if ( Failed( ::CoInitialize( NULL ) ) )
    return 1;

HRESULT hr = S_OK;

CComPtr<ISpVoice> cpVoice; //Will send data to ISpStream
CComPtr<ISpStream> cpstream; //Will contain IStream
CComPtr<IStream> cpBaseStream; //raw data
ISpObjectToken* cpToken( NULL ); //Will set voice characteristics

GUID guidFormat;
WAVEFORMATEX* pWavFormatEx = nullptr;

hr = cpVoice.CoCreateInstance( CLSID_SpVoice );

CComPtr<ISpObjectTokenCategory> cpspCategory = NULL;
if ( SUCCEEDED( hr = SpGetCategoryFromId( SPCAT_VOICES,&cpspCategory ) ) )
{
    CComPtr<IEnumSpObjectTokens> cpspEnumTokens;
    if ( SUCCEEDED( hr = cpspCategory->EnumTokens( NULL,NULL,&cpspEnumTokens ) ) )
    {
        CComPtr<ISpObjectToken> pSpTok;
        while ( SUCCEEDED( hr = cpspEnumTokens->Next( 1,&pSpTok,NULL ) ) )
        {
            // do something with the token here; for example,set the voice
            //pVoice->SetVoice( pSpTok,FALSE );
            WCHAR *pID = NULL;
            hr = pSpTok->GetId( &pID );
            // This succeeds,pID is "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\TTS_MS_EN-US_DAVID_11.0" 
            WCHAR *pName = NULL;
            pSpTok->GetStringValue( L"name",&pName );
            // pName,pGender and pLanguage are all null
            WCHAR *pGender = NULL;
            pSpTok->GetStringValue( L"gender",&pGender );
            WCHAR *pLanguage = NULL;
            pSpTok->GetStringValue( L"language",&pLanguage );
            LONG index = 0;
            WCHAR *key = NULL;
            while ( SUCCEEDED( hr = pSpTok->EnumKeys( index,&key ) ) )
            {
                // Gets some elements
                WCHAR* pValue = NULL;
                pSpTok->GetStringValue( key,&pValue );
                // Loops once,key value is "Attributes"
                index++;
            }
            index = 0;
            while ( SUCCEEDED( hr = pSpTok->EnumValues( index,&key ) ) )
            {
                // Loops many times,but none of these have what I need
                WCHAR* pValue = NULL;
                pSpTok->GetStringValue( key,&pValue );
                index++;
            }
            // NOTE:  IEnumSpObjectTokens::Next will *overwrite* the pointer; must manually release
            pSpTok.Release();
        }
    }
}

我是 Windows C++ 开发新手,抱歉。

解决方法

获得代表语音的ISpObjectToken后,检索其“Attributes”子键,然后查询子键的值:

CComPtr<ISpObjectToken> pSpTok;
while (cpSpEnumTokens->Next(1,&pSpTok,NULL) == S_OK)
{
    CComPtr<ISpDataKey> cpSpAttributesKey;
    if (SUCCEEDED(hr = pSpTok->OpenKey(L"Attributes",&cpSpAttributesKey)))
    {
        CSpDynamicString dstrName;
        cpSpAttributesKey->GetStringValue(L"Name",&dstrName);
        CSpDynamicString dstrGender;
        cpSpAttributesKey->GetStringValue(L"Gender",&dstrGender);
        // dstrName: Microsoft David Desktop
        // dstrGender: Male
    }
    pSpTok.Release();
}

我使用 CSpDynamicString 以便自动释放为字符串分配的内存。您可以选择改用 WCHAR*,但您需要自行负责调用 CoTaskMemFree。

我还修复了您原始代码中的另一个错误:cpSpEnumTokens->Next 的结果应该与 S_OK 进行比较,而不是传递给 SUCCEEDED,因为 Next 返回 S_FALSE 以指示枚举已完成。 S_FALSE 是成功的结果,因此使用 SUCCEEDED 会导致无限循环。

参考: Object Tokens and Registry Settings White Paper - §5.3 检查令牌的底层密钥