This project has moved. For the latest updates, please go here.

Resample WAV in memory

Nov 4, 2013 at 7:49 PM
Edited Nov 4, 2013 at 8:18 PM
I'd like to be able to get the output of the WaveFormatConversionStream as a byte array so I can write it to a database. In the code below, I'm writing it to disk, then reading it back from the disk and it works, but when I try to read it to a byte array, the array is all zeros.
var wfr = new WaveFileReader(file.InputStream);
WaveStream pcmStream = new WaveFormatConversionStream(new WaveFormat(44100, 16, 1), wfr);
WaveFileWriter.CreateWaveFile(@"C:\some\path\output.wav", pcmStream);
var fileBytes = System.IO.File.ReadAllBytes(@"C:\some\path\output.wav");
Coordinator
Nov 4, 2013 at 8:04 PM
what happens if you try to play the WAV file? at the very least there should be a RIFF header, even if there is complete silence in the audio.
Nov 4, 2013 at 8:07 PM
The code above works and the WAV file is valid, but I'd like to skip the last two lines (writing it to disk and then reading it back in) and instead write it from the WaveFormatConversionStream to a byte array which I can use to put into the database.
Nov 4, 2013 at 8:39 PM
Edited Nov 4, 2013 at 8:42 PM
...in other words, I'm trying to tweak the above working code that relies on write access to the file system to something like the following code which does NOT currently work but wouldn't need to use the file system:
byte[] fileBytes;
using (var wfr = new WaveFileReader(file.InputStream))
{
    using (var pcmStream = new WaveFormatConversionStream(new WaveFormat(44100, 16, 1), wfr))
    {
        fileBytes = new byte[pcmStream.Length];
        pcmStream.Read(fileBytes, 0, Convert.ToInt32(pcmStream.Length));
    }
}
This code produces an array of all zeros.
Coordinator
Nov 4, 2013 at 8:54 PM
first of all, I wouldn't recommend trying to read the entire file in one go. You're passing it through a codec, so I'd read a buffer of size pcmStream.WaveFormat.AverageBytesPerSecond. Then each buffer, write into a MemoryStream (paying attention to the return value of pcmStream.Read which tells you how many bytes it wrote into the buffer.
Nov 4, 2013 at 9:31 PM
Thanks for the advice. It's going to be really cool to have the ability to resample WAVs built in to my application when I'm done. Right now, it seem like I'm still doing something wrong or running into an issue of some kind. I updated my code to what (I think) you described and the buffer is still getting filled with all zeros. Here's how the code looks now:
byte[] fileBytes;
using (var wfr = new WaveFileReader(file.InputStream))
{
    using (var pcmStream = new WaveFormatConversionStream(new WaveFormat(44100, 16, 1), wfr))
    {
        fileBytes = new byte[pcmStream.Length];
        using (var ms = new MemoryStream(fileBytes))
        {
            var bytesRead = -1;
            while (bytesRead != 0)
            {
                var buffer = new byte[pcmStream.WaveFormat.AverageBytesPerSecond];
                bytesRead = pcmStream.Read(buffer, 0, pcmStream.WaveFormat.AverageBytesPerSecond);
                ms.Write(buffer, 0, bytesRead);
            }
        }
    }
}
pcmStream.Read() returns different lengths, as you would expect, including 88200 until it gets to the end of the stream and then starts returning 0, but buffer is always full of zeros, so in the end, the memory stream is too. I also thought you might like to know that pcmStream.CanRead always returns true even when the stream reaches the end.

Thanks again for the info!
Coordinator
Nov 4, 2013 at 9:45 PM
CanRead just mean that the stream supports reading. It is always true.
There doesn't look anything obviously wrong with your code. What is wfr.WaveFormat?
Nov 4, 2013 at 9:55 PM
Thanks. You're right about CanRead. I guess I was thinking of the DataReader, not streams.

Here's a very simple Visual Studio project with the issue reproduced:
http://bit.ly/1aAB75q

To answer your question, wfr.WaveFormat is:
{16 bit PCM: 48kHz 2 channels}
    [NAudio.Wave.WaveFormatExtraData]: {16 bit PCM: 48kHz 2 channels}
    AverageBytesPerSecond: 192000
    BitsPerSample: 16
    BlockAlign: 4
    Channels: 2
    Encoding: Pcm
    ExtraSize: 0
    SampleRate: 48000
Coordinator
Nov 4, 2013 at 9:59 PM
OK, I'm surprised WaveFormatConversionStream didn't throw an exception. Normally you can't change channel count and sample rate at the same time. Start off just trying to resample (go to stereo 44.1kHz). If that works, go to mono in the next step (easiest way is throw away every other pair of bytes).
Nov 4, 2013 at 10:14 PM
Mark,

I tried changing only the sample rate kHz. It still results in a buffer with only zeros
http://bit.ly/HD0s2J
using (var pcmStream = new WaveFormatConversionStream(new WaveFormat(44100, wfr.WaveFormat.BitsPerSample, wfr.WaveFormat.Channels), wfr))
. . .
Coordinator
Nov 5, 2013 at 6:35 AM
the code you sent me isn't writing anything into fileBytes which is why the
also, your input file starts with a section of silence, which is why it seems like the conversion is just returning silence, but if you wait for the second buffer, you'll see data.
finally, don't trust the Length property on pcmStream. It calls into acmStreamSize which isn't always reliable. Just keep reading until you get 0.
var baseDir = AppDomain.CurrentDomain.BaseDirectory;

//simulating a file being posted to an MVC application
var file = new { InputStream = File.Open(Path.Combine(baseDir, "input.wav"), FileMode.Open, FileAccess.Read) };

using (var wfr = new WaveFileReader(file.InputStream))
{
    var outputFormat = new WaveFormat(44100, wfr.WaveFormat.BitsPerSample, wfr.WaveFormat.Channels);
    using (var pcmStream = new WaveFormatConversionStream(outputFormat, wfr))
    {
        using (var ms = new MemoryStream())
        {
            var bytesRead = -1;
            while (bytesRead != 0)
            {
                var buffer = new byte[pcmStream.WaveFormat.AverageBytesPerSecond];
                bytesRead = pcmStream.Read(buffer, 0, pcmStream.WaveFormat.AverageBytesPerSecond);

                // !!! BUFFER IS ALWAYS FULL OF ZEROS !!!

                ms.Write(buffer, 0, bytesRead);
            }
            File.WriteAllBytes(Path.Combine(baseDir, "output.wav"), ms.GetBuffer());
            // to make a real wav file...
            //ms.Position = 0;
            //WaveFileWriter.CreateWaveFile(Path.Combine(baseDir, "output.wav"), new RawSourceWaveStream(ms, outputFormat));
        }
    }
}
Feb 20, 2014 at 11:09 AM
Hi,

Thanks you for code. It helped a lot but I when i writeAllbytes Buffer the sound doesn't play?
Why? i want to have it in a byte array not a file.wav
Coordinator
Feb 27, 2014 at 9:26 PM
see my commented out code to make a real WAV file