【问题标题】:SAPI 5 TTS EventsSAPI 5 TTS 事件
【发布时间】:2016-07-22 10:17:56
【问题描述】:

我写信是想就有关 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 的更好方法吗?不能用manager code,最好用C++版的……

非常感谢您的帮助

编辑 1

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

你怎么看?

【问题讨论】:

  • 无法从发布的代码中看出,但是当您发送到 WAV 文件时,您是否在某处发送消息?
  • 您如何编译代码 - Visual Studio?如果有,是哪个版本?
  • @EricBrown 好吧......真的不是......我读到过,但“著名的”WaitAndPumpMessage() 函数仍在阻塞(不是吗?)
  • @GavinBrelstaff 是的,我正在使用 2015 年社区!

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


【解决方案1】:

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

所以...看看类定义:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-10
    • 1970-01-01
    • 2011-08-11
    • 2010-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多