Thursday, 21 April 2011

How to Use WaveFileWriter

In this post I will explain how to use the WaveFileWriter class that is part of NAudio. I will discuss how to use it now in NAudio 1.4 and mention some of the changes that will be coming for NAudio 1.5.

The purpose of WaveFileWriter is to allow you to create a standard .WAV file. WAV files are often thought of as containing uncompressed PCM audio data, but actually they can contain any audio compression, and are often used as containers for telephony compression types such as mu-law, ADPCM, G.722 etc.

NAudio provides a one-line method to produce a WAV file if you have an existing WaveStream derived class that can provide the data (in NAudio 1.5 it can be an IWaveProvider).

string tempFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".wav");
WaveFormat waveFormat = new WaveFormat(8000, 8, 2);
WaveStream sourceStream = new NullWaveStream(waveFormat, 10000);
WaveFileWriter.CreateWaveFile(tempFile, sourceStream);

In the above example, I am using a simple utility class as my source stream, but in a real application this might be the output of a mixer, or the output from some effects or a synthesizer. The most important thing to note is that the Read method of your source stream MUST eventually return 0, otherwise your file will keep on writing until your disk is full! So beware of classes in NAudio (such as WaveChannel32) that can be configured to always return the number of bytes asked for from the Read method.

For greater control over the data you write, you can simply use the WriteData method (renamed to “Write” in NAudio 1.5, as WaveFileWriter will inherit from Stream). WriteData assumes that you are providing raw data in the correct format and will simply write it directly into the data chunk of the WAV file. This is therefore the most general purpose way of writing to a WaveFileWriter, and can be used for both PCM and compressed formats.

byte[] testSequence = new byte[] { 0x1, 0x2, 0xFF, 0xFE };
using (WaveFileWriter writer = new WaveFileWriter(fileName, waveFormat))
{
    writer.WriteData(testSequence, 0, testSequence.Length);
}

WaveFileWriter has an additional constructor that takes a Stream instead of a filename, allowing you to write to any kind of a stream (for example, a MemoryStream). Be aware though that when you dispose the WaveFileWriter, it disposes the output stream, so use the IgnoreDisposeStream utility class to wrap the output stream if you don’t want that to happen.

One of the most commonly used bit depths for PCM WAV files is 16 bit, and so NAudio provides another WriteData overload (to be called WriteSamples in NAudio 1.5) that allows you to supply data as an array of shorts (Int16s). Obviously, this only really makes sense if you are writing to a 16 bit WAV file, but the current implementation will also try to scale the sample value for different bit depths.

short[] samples = new short[1000];
// TODO: fill sample buffer with data
waveFileWriter.WriteData(samples, 0, samples.Length);

Another consideration is that very often after applying various audio effects (even as simple as changing the volume), the audio samples stored as 32 bit floating point numbers (float or Single). To make writing these to the WAV file as simple as possible, a WriteSample function is provided, allowing you to write one sample at a time. If the underlying PCM format is a different bit depth (e.g. 16 or 24 bits), then the WriteSample function will attempt to convert the sample to that bit depth before writing it to a file. NAudio 1.5 will also feature a WriteSamples function to allow arrays of floating point samples to be written. The following example shows one second of a 1kHz sine wave being written to a WAV file using the WriteSample function:

float amplitude = 0.25f;
float frequency = 1000;

for (int n = 0; n < waveFileWriter.WaveFormat.SampleRate; n++)
{
    float sample = (float)(amplitude * Math.Sin((2 * Math.PI * n * frequency) / waveFileWriter.WaveFormat.SampleRate));
    waveFileWriter.WriteSample(sample);
}
Post a Comment