SAPI 5 TTS 事件

SAPI 5 TTS Events

我写信是想就 SAPI 引擎的特定问题向您提出一些建议。我有一个可以对扬声器和 WAV 文件说话的应用程序。我还需要注意一些事件,即单词边界和结束输入。

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

为了测试我添加了所有事件!当引擎与扬声器对话时,所有事件都会被触发并发送到 m_hWnd window,但是当我将输出设置为 WAV 文件时,其中的 none 会被发送

    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 的更好方法吗?无法使用manager代码,应该用C++版本的比较好...

非常感谢您的帮助

编辑 1

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

你怎么看?

我采用了一个我认为在大多数情况下应该可以接受的即时解决方案,事实上,当你在文件上写演讲时,你会意识到的主要事件是 "stop" 事件.

所以...看看 class 定义:

    #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;
    }

您如何看待这个解决方案?太复杂了?