我写信是为了询问有关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 ++版本应该更好......
非常感谢您的帮助
这似乎是一个线程问题并在spuihelp.h
文件中搜索,其中包含SPBindToFile
帮助器,我发现它使用CoCreateInstance()
函数来创建流。也许这就是ISpVoice
对象失去在其创建线程中发送事件的能力的地方。
你怎么看?
答案 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;
}
您对此解决方案有何看法?太复杂了?