Never-Ending Audio Streams in NAudio
When you play audio with NAudio, you pass an audio stream (which is an implementation of IWaveProvider
) to the output device. The output device will call the Read
method many times a second asking for new buffers of audio to play. When the Read
method returns 0, that means we’ve reached the end of the stream, and playback will stop.
So for example, the audio file reader classes in NAudio such as WavFileReader
, Mp3FileReader
, AudioFileReader
etc, all implement IWaveProvider
and their Read
method returns the number of bytes asked for until the the end is reached, after which it returns 0 and playback will stop. Because these classes also inherit from WaveStream
they also support repositioning, so if you repositioned back to the start just before reaching the end, you’d be able to keep playback going for longer than the duration of the file.
But some classes in NAudio produce never-ending streams of audio. For example the SignalGenerator
class is an ISampleProvider
which continuously produces a signal such as a sine wave. If you pass this to a playback device (you can pass either IWaveProvider
or ISampleProvider
to an output device in NAudio), playback will continue indefinitely because you’ve given it a never-ending stream.
There are also some classes in NAudio whose behaviour is configurable. The MixingSampleProvider
and BufferedWaveProvider
are like this. If their ReadFully
property is set to true, they will always return the number of bytes/samples asked for in the Read
method. This is off by default with MixingSampleProvider
, meaning that once you’ve reached the end of all the inputs to the mixer, playback will end. But if you turn it on, then it means you’ll continue to play silence even though the mixer has no more inputs. This can be useful if you want to dynamically add more inputs to the mixer later on.
With BufferedWaveProvider
, ReadFully
is set to true by default. That’s because it’s designed to help you play audio you receive over the network. If there’s audio in the buffer, it gets played, but if there’s no audio in the buffer (maybe because of poor network connectivity), we don’t want to stop playback, we just want to play silence until we’ve received some audio to play.
It’s possible to take an never-ending stream and give it a finite duration. A good example of this is the OffsetSampleProvider
which can “take” a set duration of audio from an input sample provider. There are some extension methods in NAudio to make this easy to use. So for example, to get 5 seconds of a 500Hz sine wave you can do this:
var sine5Seconds = new SignalGenerator() { Gain = 0.2, Frequency = 500 }.Take(TimeSpan.FromSeconds(5));
If you play this, it will stop after 5 seconds:
using (var wo = new WaveOutEvent())
{
wo.Init(sine5Seconds);
wo.Play();
while (wo.PlaybackState == PlaybackState.Playing)
{
Thread.Sleep(500);
}
}
You can also go the other way, and make make a regular wave provider endless either by extending it with silence, or by looping it. I’ve written before on how to implement looping in NAudio.
Hopefully this has helped clarify why in NAudio playback sometimes doesn’t stop when you wanted it to (you accidentally created an endless stream), or how you can can keep a single playback session running continuously without needing to keep opening and closing the output device. This allows you to easily implement a fire and forget audio playback engine where you can play sounds by adding them to a mixer, which will just produce a never-ending stream of silence if no sounds are currently active. So never-ending streams can be a good thing.
“But let justice roll on like a river, righteousness like a never-ending stream!” Amos 5:24
Comments
This post saved my day
juniorGY