Speaking with an ISpVoice from a ISpTTSEngine

Viewed 129

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?

1 Answers

SAPI isn't expecting to be entered recursively. Consider using a different TTS engine (e.g., the WinRT System.Media.SpeechSynthesis APIs) to do the actual synthesis. The text fragments won't have any embedded markup, so that won't be a big deal.

Related