How to change bits per sample

Sep 3, 2014 at 10:52 AM
Edited Sep 3, 2014 at 10:54 AM
Hi there,
I've some stereo, 32 bit pcm audio wav file loaded as an IWaveSource as follows:
var codec = CodecFactory.Instance.GetCodec(myFile.wav).ToMono();
I would like to get another wav file in 16 bit pcm format. How can I do that?

In addition, I want to record from a microphone directly in 16 bit pcm mono. I have this piece of code:
public void RecordToFIle(String filename)
             {
              var wasapiIn = new WasapiCapture
                       {
                       Device = devices.First() // devices is IList<MMDevice>
                       };

            wasapiIn.Initialize();

            writer = new WaveWriter(filename, wasapiIn.WaveFormat);
            wasapiIn.DataAvailable += NewAudioData;

            wasapiIn.Start();
            Console.WriteLine("Press any key to stop...");
            Console.ReadKey();
            wasapiIn.Stop();

            writer.Dispose();
            wasapiIn.Dispose();
            }

private void NewAudioData(Object s, DataAvailableEventArgs e)
            {
            writer.Write(e.Data, e.Offset, e.ByteCount);
            }
If i change
 writer = new WaveWriter(filename, wasapiIn.WaveFormat);
with
 writer = new WaveWriter(filename, myNewWaveFormat);
recorded sound is awful. How can I configure recording WaveFormat?

Kind regards.
Coordinator
Sep 3, 2014 at 12:11 PM
Edited Sep 3, 2014 at 12:11 PM
I would like to get another wav file in 16 bit pcm format. How can I do that?
Its the same code as always:
var codec = CodecFactory.Instance.GetCodec(your16bitpcmfile.wav);
In addition, I want to record from a microphone directly in 16 bit pcm mono.
First of all you can try to specify a default format. If it won't, you have to change the bps manually:
private static void Main(string[] args)
        {
            const int targetBitsPerSample = 16;

            WasapiCapture capture = null;
            IWaveSource soundInSource = null;
            WaveWriter waveWriter = null;
            try
            {
                capture = new WasapiCapture(false, AudioClientShareMode.Shared, 100,
                    new WaveFormat(44100, targetBitsPerSample, 2, AudioEncoding.Pcm));

                capture.Initialize();
                soundInSource = new SoundInSource(capture);

                if (capture.WaveFormat.BitsPerSample != targetBitsPerSample)
                {
                    soundInSource = soundInSource.ToSampleSource().ToWaveSource(targetBitsPerSample);
                }

                waveWriter = new WaveWriter("out.wav", soundInSource.WaveFormat);

                byte[] buffer = new byte[soundInSource.WaveFormat.BytesPerSecond / 2];
                capture.DataAvailable += (s, e) =>
                {
                    int read = soundInSource.Read(buffer, 0, buffer.Length);
                    if (read > 0)
                    {
                        waveWriter.Write(buffer, 0, read);
                    }
                };

                capture.Start();

                Console.WriteLine("Press any key to stop...");
                Console.ReadKey();
            }
            finally
            {
                if(capture != null)
                    capture.Dispose();
                if(soundInSource != null)
                    soundInSource.Dispose();
                if(waveWriter != null)
                    waveWriter.Dispose();
            }
        }
Sep 4, 2014 at 7:54 AM
Edited Sep 4, 2014 at 7:55 AM
Thanks for your fast response. I've tried your example but I still have some problems. In:
 capture = new WasapiCapture(false, AudioClientShareMode.Shared, 100,
                    new WaveFormat(44100, targetBitsPerSample, 2, AudioEncoding.Pcm));
if I change the '2' (numChannels) recorded audio sounds different (low/high-pitched), but the number of channels is correct. When this value is set to 2 channels recorded audio is fine. I tried this and it works, it sounds fine with any number of channels (in my code I use same try-catch statements as you):
var format = format = new WaveFormat(44100, 16, numChannels, AudioEncoding.Pcm);
WaveInEvent capture = new WaveInEvent(format);

capture.DataAvailable += (s, e) =>
                {
                 writer.Write(e.Data, e.Offset, e.ByteCount);
                };

capture.Initialize();
capture.Start();

Console.WriteLine("Press any key to finish...");
Console.ReadKey();

capture.Stop();
What is the difference if I use WaveInEvent instead of WasapiCapture?
Coordinator
Sep 4, 2014 at 9:02 AM
I still don't really get your problem. As far as I could see, you are trying to change the number of channels which causes a bad result which WasapiCapture but not with WaveIn? I would not use WaveIn because it is quite obsolete. If the WasapiCapture implementation contains any bugs, please give me a detailed description and I will fix them.
But I am afraid of that I still don't really understand your problem :(.
Sep 4, 2014 at 10:25 AM
Edited Sep 4, 2014 at 10:25 AM
Okay, I'll try to explain it better.

Suppose I'm trying the code you wrote yesterday:
private static void Main(string[] args)
        {
            const int targetBitsPerSample = 16;

            WasapiCapture capture = null;
            IWaveSource soundInSource = null;
            WaveWriter waveWriter = null;
            try
            {
                capture = new WasapiCapture(false, AudioClientShareMode.Shared, 100,
                    new WaveFormat(44100, targetBitsPerSample, 2, AudioEncoding.Pcm));

                capture.Initialize();
                soundInSource = new SoundInSource(capture);

                if (capture.WaveFormat.BitsPerSample != targetBitsPerSample)
                {
                    soundInSource = soundInSource.ToSampleSource().ToWaveSource(targetBitsPerSample);
                }

                waveWriter = new WaveWriter("out.wav", soundInSource.WaveFormat);

                byte[] buffer = new byte[soundInSource.WaveFormat.BytesPerSecond / 2];
                capture.DataAvailable += (s, e) =>
                {
                    int read = soundInSource.Read(buffer, 0, buffer.Length);
                    if (read > 0)
                    {
                        waveWriter.Write(buffer, 0, read);
                    }
                };

                capture.Start();

                Console.WriteLine("Press any key to stop...");
                Console.ReadKey();
            }
            finally
            {
                if(capture != null)
                    capture.Dispose();
                if(soundInSource != null)
                    soundInSource.Dispose();
                if(waveWriter != null)
                    waveWriter.Dispose();
            }
        }
Let's try some tests:

Test 1: Just run your code. I get an audio file named "out.wav" with fs = 44100 Hz, 2 channels, 16 bit pcm and if I play it (for instance with windows media player) it sounds fine.

Test 2: Now I change number of channels to one modifying this line of code:
 capture = new WasapiCapture(false, AudioClientShareMode.Shared, 100,
                    new WaveFormat(44100, targetBitsPerSample, 1, AudioEncoding.Pcm)); // One channel instead or two 
If i run that code I get an audio file named "out.wav" with fs = 44100 Hz, 2 channels, 16 bit pcm. Why 2 channels?

Test 3: Let´s do one little modification (here is my low/high-pitched audio problem). There are a pair of changes in your initial code:
// Define format
var format = new WaveFormat(44100, targetBitsPerSample, 1, AudioEncoding.Pcm); /* one channel*/

capture = new WasapiCapture(false, AudioClientShareMode.Shared, 100, format); /* Same line as yours, just format defined before*/

// More code...

waveWriter = new WaveWriter("out.wav", format); /* I changed soundInSource.WaveFormat with format (same WaveFormat in capture & write)*/
If i run that code I get an audio file named "out.wav" with fs = 44100 Hz, __mono (this is what i want) __ 16 bit pcm and if I play it it sounds low pitched, as if fs was not correct. If i set numChannels to 4, for example, output audio sounds high-pitched.

I hope you can understand my problem.

Kind regards.
Coordinator
Sep 4, 2014 at 11:01 AM
Edited Sep 4, 2014 at 11:01 AM
Ok. I've got it. You misunderstood the WaveFormat argument of the WasapiCapture ctor. I wrote:
First of all you can try to specify a default format.
You can try to specify a default format. Behind the scenes the WasapiCapture class tries to use the specified default format. But if the audio driver refuses the default format, the actual format of the recorded data can be something else. The actual output format is available after calling the WasapiCapture.Initialize method (see WasapiCapture.WaveFormat property).
That's the reason why you can't rely on the specified default format. I your case, you are telling the audio driver that you would prefer only one channel, but the audio driver refuses that wish and offers you only audio data with two channels.
So, if the audio driver refuses your wishes you have to convert the captured audio data yourself. If you want only one channel you can modify my code:
private static void Main(string[] args)
{
    const int targetBitsPerSample = 16;
    const int numberOfChannels = 1;

    WasapiCapture capture = null;
    IWaveSource soundInSource = null;
    WaveWriter waveWriter = null;
    try
    {
        capture = new WasapiCapture(false, AudioClientShareMode.Shared, 100,
            new WaveFormat(44100, targetBitsPerSample, numberOfChannels, AudioEncoding.Pcm));

        capture.Initialize();
        soundInSource = new SoundInSource(capture);

        if (capture.WaveFormat.BitsPerSample != targetBitsPerSample)
        {
            soundInSource = soundInSource.ToSampleSource().ToWaveSource(targetBitsPerSample);
        }
        if(soundInSource.WaveFormat.Channels != 1)
        {
            soundInSource = soundInSource.ToMono();
        }

        waveWriter = new WaveWriter("out.wav", soundInSource.WaveFormat);

        byte[] buffer = new byte[soundInSource.WaveFormat.BytesPerSecond / 2];
        capture.DataAvailable += (s, e) =>
        {
            int read = soundInSource.Read(buffer, 0, buffer.Length);
            if (read > 0)
            {
                waveWriter.Write(buffer, 0, read);
            }
        };

        capture.Start();

        Console.WriteLine("Press any key to stop...");
        Console.ReadKey();
    }
    finally
    {
        if(capture != null)
            capture.Dispose();
        if(soundInSource != null)
            soundInSource.Dispose();
        if(waveWriter != null)
            waveWriter.Dispose();
    }
}
Sep 4, 2014 at 11:08 AM
Okay, I think I've got it. I'll try that code later, thank you so much!
Sep 24, 2014 at 2:32 PM
Hi there, I'm here again.

I have a doubt regarding this piece of code:
byte[] buffer = new byte[soundInSource.WaveFormat.BytesPerSecond / 2];
        capture.DataAvailable += (s, e) =>
        {
            int read = soundInSource.Read(buffer, 0, buffer.Length);
            if (read > 0)
            {
                waveWriter.Write(buffer, 0, read);
            }
        };
I can't understand what happens with the offset parameter. In
waveWriter.Write(buffer, 0, read);
line shouldn't we set offset to 'e.Offset'? If not, why should it be set to 0 every time the event fires?.

Kind regards.
Coordinator
Sep 24, 2014 at 7:41 PM
No you don't have to. You are not using the audio data provided by the WasapiCapture directly. Instead you are getting the data from the soundInSource which changes the bits per sample. If you would use e.Offset, you would also use the e.Data and the e.Count parameters. But you are reading the data yourself with your OWN buffer. So the offset will be always zero.
Sep 25, 2014 at 8:19 AM
Okay I didn't realize that. Thanks!!!!