Wednesday 2 July 2014

Input Driven Resampling with NAudio using ACM

NAudio provides a number of different mechanisms for resampling audio, but the resampler classes all assume you are doing “output driven” resampling. This is where you know how much resampled audio you want, and you “pull” the necessary amount of input audio through. This is fine for playing back audio, or for resampling existing audio files, but there are cases when you want to do “input driven” resampling. For example, if you are receiving blocks of audio from the network or soundcard, and want to resample just that block of audio before sending it on somewhere else, then input driven is what you want.

All of NAudio’s resamplers (ACM, Media Foundation and WDL) can be used in input driven mode, but in this post I’ll focus on using AcmStream, since it is the most widely available, and doesn’t require floating point samples.

Step 1 – Open an ACM Resampler Stream

We start by opening the ACM resampler, specifying the desired input and output formats. Here, we open an ACM stream that can convert mono 16 bit 44.1kHz to mono 16 bit 8kHz. (Note that the ACM resampler won’t change bit depths or channel counts at the same time – so the WaveFormats should differ by sample rate only).

var resampleStream = new AcmStream(new WaveFormat(44100, 16, 1), new WaveFormat(8000, 16, 1));

Note that you shouldn’t recreate the AcmStream for each buffer you receive. Codecs like to maintain internal state, so you’ll get the best results if you open it once, and then keep passing audio through it.

Step 2 – Copy Audio into the Source Buffer

Now with the ACM stream, it already has a source and destination buffer allocated that it will use for all conversions. So we need to copy audio into the source buffer, Convert it, then read it out of the destination buffer. The source buffer is a limited size, so if you have more input data than can fit into the source buffer, you’ll need to perform this conversion in multiple stages. Here we’ll get some input data as a byte array (e.g. received from the network or a soundcard), and copy it into the source buffer:

byte[] source = GetInputData();
Buffer.BlockCopy(source, 0, resampleStream.SourceBuffer, 0, source.Length);

Step 3 – Resample the Source Buffer with the Convert function

To convert the audio in the source buffer, call the Convert function. This will return the number of bytes available in the destination buffer for you to read out. It will also tell you how many source bytes were used. This should be all of them. If they haven’t been used, it probably means you are trying to convert too many at once. Pass the audio through in smaller block sizes, or copy the “leftovers” out and put them through again next time (this is how NAudio’s WaveFormatConversionStream works internally). Here’s the code to convert what you’ve copied into the source buffer:

int sourceBytesConverted = 0;
var convertedBytes = resampleStream.Convert(source.Length, out sourceBytesConverted);
if (sourceBytesConverted != source.Length)
{
    Console.WriteLine("We didn't convert everything {0} bytes in, {1} bytes converted");
}

Step 4 – Read the Resampled Audio out of the Destination Buffer

Now we can copy our converted audio out of the ACM stream’s destination buffer:

var converted = new byte[convertedBytes];
Buffer.BlockCopy(resampleStream.DestBuffer, 0, converted, 0, convertedBytes);

And that’s all you have to do (apart from remember to Dispose your AcmStream when you’re done with it). It is a little more convoluted than output driven resampling, but not too difficult once you know what you’re doing.

I’ll try to do some future posts showing how to do input driven resampling with the Media Foundation resampler, and the WDL resampler.

No comments: