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.
Comments
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 SailmanClipping 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 HeathHey 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 SchultzFirstly, I can't thank you enough for naudio! Simply brilliant.
Martin WeetmanI 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?
The media foundation resampler is much better quality than ACM
Mark HeathHello Mark,
xGxIs this also possible using wdl?
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