Posted in:

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.

Want to get up to speed with the the fundamentals principles of digital audio and how to got about writing audio applications with NAudio? Be sure to check out my Pluralsight courses, Digital Audio Fundamentals, and Audio Programming with NAudio.

Comments

Comment by Mike Sailman

Hey Mark, Great Post. I think I need something like this, but I'm stuck. I am capturing audio from 2 different WasapiLoopbackCapture devices and I am Merging them together using the MixingSampleProvider all in a MemoryStream. I am writing to a file at the end so I can listen and make sure the audio sounds good. I am hearing clipping in my resulting file. I assume this is from the sources not being in the same format? Any Help would be awesome as I've been struggling with this for a while.

Mike Sailman
Comment by Mark Heath

Clipping can just be that the value of the mixed floating point samples goes over 1. You can reduce the volume of all the inputs a little bit to work around this. If the audio formats were different you would hear much worse audio artefacts than clipping.

Mark Heath
Comment by Josh Schultz

Hey this post is just the answer to my question! I'm trying to convert the wasapiloopbackcapture audio to 1 channel (instead of 2). But I'm a bit confused, what is GetInputData(); ?

Josh Schultz
Comment by Martin Weetman

Firstly, I can't thank you enough for naudio! Simply brilliant.
I am resampling a stream using your example. The source is 16 bits 8KHz mono, & I am resmpling to 16 bits, 16KHz mono. All seems to go well with the conversion, but the "converted" 16K stream just has every (8K) sample repeated (eg 2,2,5,5,7,7,9,9). I would have expected linear interpolation between the samples. Of course, I can filter the output with an 8K low pass filter, but I think I must be doing something wrong?

Martin Weetman
Comment by Mark Heath

The media foundation resampler is much better quality than ACM

Mark Heath
Comment by xGx

Hello Mark,
Is this also possible using wdl?

xGx
Comment by Yves Goergen

So this example uses the AcmStream class and the media foundation resampler "is much better quality". How would I use that instead? I can't find the necessary classes or namespaces. I only have the NAudio.Wasapi package installed, so the ACM stuff isn't even available, and I think I don't want it additionally. I need to convert audio captured from Wasapi loopback (currently 2x32 bit float, 48 kHz) to PCM int 2x16 bit, 44.1 kHz for sending it out to the network in real time. Latest Windows 10 here, so it should be available.

Yves Goergen