I'm implementing an ISpTTSEngine for the Microsoft Speech API (SAPI). I'd like for this voice to annunciate just like a typical TTS voice. Rather than write my own speech synthesizer, I'd like to delegate to a built-in ISpVoice.
I've written enough code to hear text vocalized, but it has a major deficiency
that I haven't been able to explain: the speech does not begin until after my
implementation of ISpTTSEngine:Speak has returned. For the duration of the
audible output, my implementation of ISpTTSEngine:Speak is not invoked, even
when the software using the TTS voice is sending requests.
(For context: my goal for this project is to programmatically observe the speech data that other pieces of software are attempting to vocalize. That part appears to be working as intended.)
The full source is available here. I'll try to summarize with the most relevant parts.
My implementation of ISpTTSEngine has a private member named
m_cpVoice:
class ATL_NO_VTABLE CTTSEngObj :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CTTSEngObj, &CLSID_SampleTTSEngine>,
public ISpTTSEngine,
public ISpObjectWithToken
{
// ...
private:
CComPtr<ISpVoice> m_cpVoice;
And it is initialized in the FinalConstruct
method:
HRESULT CTTSEngObj::FinalConstruct()
{
HRESULT hr = S_OK;
// ...
hr = m_cpVoice.CoCreateInstance(CLSID_SpVoice);
My implementation of ISpTTSEngine:Speak iterates over the text fragments it
receives
and passes the text data to the ISpVoice::Speak
method:
STDMETHODIMP CTTSEngObj::Speak(DWORD dwSpeakFlags,
REFGUID rguidFormatId,
const WAVEFORMATEX* pWaveFormatEx,
const SPVTEXTFRAG* pTextFragList,
ISpTTSEngineSite* pOutputSite)
{
// ...
for (const SPVTEXTFRAG* textFrag = pTextFragList; textFrag != NULL; textFrag = textFrag->pNext)
{
// ...
const std::wstring& text = textFrag->pTextStart;
hr = m_cpVoice->Speak(text.substr(0, textFrag->ulTextLen).c_str(), dwSpeakFlags | SPF_ASYNC | SPF_PURGEBEFORESPEAK, 0);
As mentioned above, no audio is emitted until after ISpTTSEngine:Speak
returns. An arbitrary sleep statement demonstrates this most clearly. Polling
the ISpVoice's SpeakCompleteEvent handle inevitably times out. Removing the
SPF_ASYNC flag from the invocation of ISpVoice::Speak causes the caller to
crash.
Can anyone explain this behavior? Or suggest a change that would allow me to observe subsequent speech requests?