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;
}
您如何看待这个解决方案?太复杂了?
我写信是想就 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;
}
您如何看待这个解决方案?太复杂了?