Search code examples
c#text-to-speechnaudio

Is there a way to Play a WAV File Synchronously in NAudio?


I might be wrong on this, but it appears that my WAV file in NAudio is playing asynchronously, and if I call it from a library, I can hear the WAV start, but it exits (and stops playing) almost immediately.

If I run it from a Form (which stays open) it is fine. But if I try to run it from code as a background thread, I only hear a small fragment in the beginning. It SEEMS like the method is exiting before the sound is finished playing.

I am using the Text To Speech Synthesizer to create the WAV on a Memory stream:

        SpeechSynthesizer m_speechSynth = new SpeechSynthesizer();
        m_speechSynth.Volume = 100;
        m_speechSynth.Rate = 2;

        MemoryStream waveStream = new MemoryStream();

        m_speechSynth.SetOutputToWaveStream(waveStream);
        m_speechSynth.Speak(txtToRead);
        m_speechSynth.SetOutputToNull();

        waveStream.Position = 0; // reset counter to start

That works fine. I then create a class that I wrapped around NAudio:

VoiceThroughNetAudio netAudio = new VoiceThroughNetAudio(waveStream, "WAV");

I'm trying to keep the code sample here easy to follow... but what this class does is simply initializes things, and gives me a higher level of control to do the main things on an NAudio player... Inside the VoiceThroughNetAudio constructor,

First I create a reader Stream from the MemStream:

            readerStream = new WaveFileReader(memStream);
            readerStream = WaveFormatConversionStream.CreatePcmStream(readerStream);
            readerStream = new BlockAlignReductionStream(readerStream);

And attach a Volume Control (so I can amplify it):

_volumeStream = new WaveChannel32(Reader);

This all works fine when I call it from my WinForm Code:

        MemoryStream waveStream = new MemoryStream();
        ReadStringAmplified(readTextField.Text, waveStream); // TTS to waveStream
        VoiceThroughNetAudio  PlaySound = new VoiceThroughNetAudio (waveStream, "WAV");
        PlaySound.Volume = 9;
        PlaySound.Play();

But when I move this code into the Voice class instead, the sound STARTS to play, and then stops (almost immediately). Like it is starting an event and then exiting before the sound finishes? If so, is there a good way of making the method wait until the sound is finished? (I tried putting a while loop in and check the state, but this just ends up going into the while loop for an infinite period (commented out here...)

Here is how the code looks when I move it to the Voice Class. If I instantiate it from the form, and run it all there, and I get the truncated sound:

        SpeechSynthesizer m_speechSynth = new SpeechSynthesizer();
        m_speechSynth.Volume = 100;
        m_speechSynth.Rate = 2;

        MemoryStream waveStream = new MemoryStream();
        m_speechSynth.SetOutputToWaveStream(waveStream);
        m_speechSynth.Speak(txtToRead);
        m_speechSynth.SetOutputToNull();
        waveStream.Position = 0; // reset counter to start

        VoiceThroughNetAudio netAudio = new VoiceThroughNetAudio(waveStream, "WAV");
        netAudio.Volume = 9;
        TimeSpan ts = netAudio.TotalDuration;
        string len = ts.Milliseconds.ToString();

        netAudio.Play();

        //VoiceThroughNetAudio.PlayBackState chkState = netAudio.State;
        //while (netAudio.State != VoiceThroughNetAudio.PlayBackState.Stopped)
        //{
        //    chkState = netAudio.State;
        //}

It seems like I am missing something simple, but I can't seem to get this to work in the Voice Class, only from the Form.


Solution

  • Most likely explanations for your problem are:

    1. You are trying to play from a MemoryStream that is still being written to
    2. Your output device has gone out of scope and been garbage collected while it is still playing

    Try subscribing to the PlaybackStopped event of your IWavePlayer to see when and why it is firing (it can contain an exception if there is a problem).

    Also, I very much doubt you need a WaveFormatConversionStream and a BlockAlignReductionStream, so simplify your code by removing those.