0

I'm working on a method to resample a wav file, here's the method:

internal byte[] ResampleWav(byte[] rawPcmData, int frequency, int bits, int channels, int newFrequency)
{
    byte[] pcmData;

    using (MemoryStream AudioSample = new MemoryStream(rawPcmData))
    {
        RawSourceWaveStream Original = new RawSourceWaveStream(AudioSample, new WaveFormat(frequency, bits, channels));
        using (MediaFoundationResampler conversionStream = new MediaFoundationResampler(Original, new WaveFormat(newFrequency, bits, channels)))
        {
            // Here should go the code to get the array of bytes with the resampled PCM data
        }
    }

    return pcmData;
}

The problem here is that there isn't any property in the MediaFoundationResampler that returns the size of the buffer. The method should return an array of bytes with the resampled PCM data only.

Thanks in advance!

--Edit

After some time working, I could get this:

internal byte[] WavChangeFrequency(byte[] rawPcmData, int frequency, int bits, int channels, int newFrequency)
{
    byte[] pcmData;
    using (MemoryStream AudioSample = new MemoryStream(rawPcmData))
    {
        RawSourceWaveStream Original = new RawSourceWaveStream(AudioSample, new WaveFormat(frequency, bits, channels));
        using (MediaFoundationResampler conversionStream = new MediaFoundationResampler(Original, newFrequency))
        {

            //Start reading PCM data
            using (MemoryStream wavData = new MemoryStream())
            {
                byte[] readBuffer = new byte[1024];
                while ((conversionStream.Read(readBuffer, 0, readBuffer.Length)) != 0)
                {
                    wavData.Write(readBuffer, 0, readBuffer.Length);
                }
                pcmData = wavData.ToArray();
            }
        }
    }
    return pcmData;
}

"Seems" to work fine, but there's another problem, seems that the PCM data byte array is greater than expected. Here's one of the tests I've tested with the method:

Input settings:

44100Hz
16 Bits
01 Channel 
1846324 Bytes of PCM data

Expected (when I resample the same wav file with Audition, Audacity and WaveFormatConversionStream I get this):

22050Hz
16 Bits
01 Channel 
923162 Bytes

MediaFoundationResampler result:

22050Hz
16 Bits
01 Channel 
923648 Bytes

And the size changes drastically if I change the size of the readBuffer array.

The main problem is that MediaFoundationResampler doesn't have the property Length to know the real size of the resampled PCM data buffer. Using WaveFormatConversionStream the code would be this, but the quality is not very good:

internal byte[] WavChangeFrequency(byte[] rawPcmData, int frequency, int bits, int channels, int newFrequency)
{
    byte[] pcmData;

    using (MemoryStream AudioSample = new MemoryStream(rawPcmData))
    {
        RawSourceWaveStream Original = new RawSourceWaveStream(AudioSample, new WaveFormat(frequency, bits, channels));

        using (WaveFormatConversionStream wavResampler = new WaveFormatConversionStream(new WaveFormat(newFrequency, bits, channels), Original))
        {
            pcmData = new byte[wavResampler.Length];
            wavResampler.Read(pcmData, 0, pcmData.Length);
        }
    }

    return pcmData;
}

What should I do to get the expected PCM data array, using the MediaFoundationResampler?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
jms2505
  • 68
  • 8
  • Are you sure you cannot calculate the new length using your method’s parameters frequency and newFrequency? – Laurent Gabiot Nov 07 '21 at 23:59
  • Well, probably yes, but I'm not sure about how to do it. What would the formula be? – jms2505 Nov 08 '21 at 00:02
  • If the only change is the sampling frequency, something like: var NewLength = rawPcmData.Length * (newFrequency / frequency); There will most probably be some length rounding issues. I don’t know this library, so this might be a bad way to solve your problem. – Laurent Gabiot Nov 08 '21 at 00:18
  • Tested it, but doesn't work, not sure why but gives zero. Anyway thank you! – jms2505 Nov 08 '21 at 14:13
  • Strange, I did a quick test and it seemed to work. I'll try again tonight when I'll have time. – Laurent Gabiot Nov 08 '21 at 14:59

1 Answers1

0

Disclaimer

I'm not familiar with the NAudio Library, so there might be a more proper way of doing this.

EDIT

Still not a good answer, seems still off by a few bytes... Some corrections to the code, using Mark Heath (NAudio creator) comment on this answer: https://stackoverflow.com/a/14481756/9658671

I keep the answer here for now, as it might help for finding a real answer, but I'll edit or remove it if necessary.

/EDIT

The difference in length between the file produced by Audition and your code is 923648 - 923162 = 486 bytes, which is less than your 1024 buffer.

It can be explained by the following mechanism:

At the very last call to the Read method, the remaining byte count is inferior to your buffer size. So instead of getting 1024 bytes, you get less.

But your code still adds a full 1024 byte group, instead of a smaller number. That explains the 486 bytes difference and the fact that this number will change if you choose another buffer size.

Fixing this should be easy.

From NAudio documentation: https://github.com/naudio/NAudio/blob/fb35ce8367f30b8bc5ea84e7d2529e172cf4c381/Docs/WaveProviders.md

The Read method returns the number for bytes that were read. This should never be more than numBytes and can only be less if the end of the audio stream is reached. NAudio playback devices will stop playing when Read returns 0.

So instead of pushing always 1024 bytes at each iteration, just push the number returned by the Read method.

Also, from Mark Heath comment:

the buffer size should be configurable to be an exact multiple of the block align of the WaveStream

So instead of choosing a "random" buffer size, use a multiple of the block align.

internal byte[] WavChangeFrequency(byte[] rawPcmData, int frequency, int bits, int channels, int newFrequency, int BlockAlign)
{
    byte[] pcmData;
    var BufferSize = BlockAlign * 1024;
    using (MemoryStream AudioSample = new MemoryStream(rawPcmData))
    {
        RawSourceWaveStream Original = new RawSourceWaveStream(AudioSample, new WaveFormat(frequency, bits, channels));
        using (MediaFoundationResampler conversionStream = new MediaFoundationResampler(Original, newFrequency))
        {

            //Start reading PCM data
            using (MemoryStream wavData = new MemoryStream())
            {
                var ByteCount = 0;
                var readBuffer = new byte[BufferSize];
                while ((ByteCount = conversionStream.Read(readBuffer, 0, readBuffer.Length)) != 0)
                {
                    wavData.Write(readBuffer, 0, ByteCount);
                }
                pcmData = wavData.ToArray();
            }
        }
    }
    return pcmData;
}
Laurent Gabiot
  • 1,251
  • 9
  • 15
  • Thank you for your response!, I've just test it, but sill happens, the array length is 923040 instead of 923162. :( – jms2505 Nov 10 '21 at 14:23