SAPI 5 TTS活动

时间:2016-04-02 16:55:26

标签: visual-c++ visual-studio-2015 text-to-speech sapi

我写信是为了询问有关SAPI引擎的特定问题的一些建议。我有一个应用程序可以说话扬声器和WAV文件。我还需要注意一些事件,即字边界和结束输入。

    m_cpVoice->SetNotifyWindowMessage(m_hWnd, TTS_MSG, 0, 0);
    hr = m_cpVoice->SetInterest(SPFEI_ALL_EVENTS, SPFEI_ALL_EVENTS);

为了测试,我添加了所有活动!当引擎与扬声器通话时,所有事件都会被触发并发送到m_hWnd窗口,但是当我将输出设置为WAV文件时,不会发送任何事件

    CSpStreamFormat fmt;  
    CComPtr<ISpStreamFormat> pOld;

    m_cpVoice->GetOutputStream(&pOld);
    fmt.AssignFormat(pOld);
    SPBindToFile(file, SPFM_CREATE_ALWAYS, &m_wavStream, &fmt.FormatId(), fmt.WaveFormatExPtr());
    m_cpVoice->SetOutput(m_wavStream, false);
    m_cpVoice->Speak(L"Test", SPF_ASYNC, 0);

其中file是作为参数传递的路径。

实际上,此代码取自SAPI SDK上的TTS示例。设置格式的部分似乎有点模糊......

你能帮我找到问题吗?或者你们中的任何人都知道将TTS写入WAV的更好方法吗?我不能使用管理器代码,使用C ++版本应该更好......

非常感谢您的帮助

编辑1

这似乎是一个线程问题并在spuihelp.h文件中搜索,其中包含SPBindToFile帮助器,我发现它使用CoCreateInstance()函数来创建流。也许这就是ISpVoice对象失去在其创建线程中发送事件的能力的地方。

你怎么看?

1 个答案:

答案 0 :(得分:0)

我采用了一种即时解决方案,我认为在大多数情况下都应该可以接受,事实上当你在文件上写下演讲时,你会发现的主要事件是&#34;停止&#34;事件。

所以......看一下班级定义:

    #define TTS_WAV_SAVED_MSG            5000
    #define TTS_WAV_ERROR_MSG            5001

    class CSpeech { 
    public:
        CSpeech(HWND); // needed for the notifications
        ...
    private:
        HWND m_hWnd;
        CComPtr<ISpVoice> m_cpVoice;
        ...
        std::thread* m_thread;

        void WriteToWave();
        void SpeakToWave(LPCWSTR, LPCWSTR);
    };

我实现了方法SpeakToWav,如下所示

    // Global variables (***)
    LPCWSTR tMsg;
    LPCWSTR tFile;
    long tRate;
    HWND tHwnd;
    ISpObjectToken* pToken;

    void CSpeech::SpeakToWave(LPCWSTR file, LPCWSTR msg) {
        // Using, for example wcscpy_s:
        // tMsg <- msg;
        // tFile <- file;

        tHwnd = m_hWnd;
        m_cpVoice->GetRate(&tRate);
        m_cpVoice->GetVoice(&pToken);

        if(m_thread == NULL)
            m_thread = new std::thread(&CSpeech::WriteToWave, this);
    }

现在......看看WriteToWave()方法:

    void CSpeech::WriteToWav() {
        // create a new ISpVoice that exists only in this
        // new thread, so we need to 
        //
        // CoInitialize(...) and...
        // CoCreateInstance(...)

        // Now set the voice, i.e. 
        //    rate with global tRate, 
        //    voice token with global pToken
        //    output format and...
        //    bind the stream using tFile as I did in the 
        //      code listed in my question

        cpVoice->Speak(tMsg, SPF_PURGEBEFORESPEAK, 0);
        ...

现在,因为我们没有使用SPF_ASYNC标志,所以调用是阻塞的,但是因为我们在一个单独的线程上,所以主线程可以继续。完成Speak()方法后,新线程可以继续如下:

        ...
        if(/* Speak is went ok */)
            ::PostMessage(tHwn, TTS_WAV_SAVED_MSG, 0, 0);
        else
            ::PostMessage(tHwnd, TTS_WAV_ERROR_MSG, 0, 0);
    }

(***)好的!使用全局变量并不是很酷:)但我的速度很快。也许使用std::reference_wrapper传递参数的线程会更优雅!

显然,在收到TTS消息时,您需要清理该线程以进行下次呼叫!这可以使用CSpeech::CleanThread()方法完成,如下所示:

    void CSpeech::CleanThread() {
        m_thread->join(); // I prefer to be sure the thread has finished!
        delete m_thread;
        m_thread = NULL;
    }

您对此解决方案有何看法?太复杂了?