Friday, 9 October 2009

Recording the Soundcard Output to WAV in NAudio

Suppose you want to not just play back some audio, but record what you are playing to a WAV file. This can be achieved in NAudio by creating an IWaveProvider whose read method reads from another IWaveProvider but also writes to disk as it goes. This is very easy to implement, and the WaveRecorder class I present here will be added to NAudio shortly. Our WaveRecorder also needs to be disposable, as we will want to close the WAV file when we are finished.
/// <summary>
/// Utility class to intercept audio from an IWaveProvider and
/// save it to disk
/// </summary>
public class WaveRecorder : IWaveProvider, IDisposable
{
    private WaveFileWriter writer;
    private IWaveProvider source;
 
    /// <summary>
    /// Constructs a new WaveRecorder
    /// </summary>
    /// <param name="destination">The location to write the WAV file to</param>
    /// <param name="source">The source Wave Provider</param>
    public WaveRecorder(IWaveProvider source, string destination)
    {
        this.source = source;
        this.writer = new WaveFileWriter(destination, source.WaveFormat);
    }
     
    /// <summary>
    /// Read simply returns what the source returns, but writes to disk along the way
    /// </summary>
    public int Read(byte[] buffer, int offset, int count)
    {
        int bytesRead = source.Read(buffer, offset, count);
        writer.WriteData(buffer, offset, bytesRead);
        return bytesRead;
    }
 
    /// <summary>
    /// The WaveFormat
    /// </summary>
    public WaveFormat WaveFormat
    {
        get { return source.WaveFormat; }
    }
 
    /// <summary>
    /// Closes the WAV file
    /// </summary>
    public void Dispose()
    {
        if (writer != null)
        {
            writer.Dispose();
            writer = null;
        }
    }
}

Now we have our WaveRecorder, we can insert it anywhere in the chain we like. The most obvious place is right at the end of the chain. So we wrap the WaveStream or WaveProvider we would normally pass to the Init method of our IWavePlayer with the WaveRecorder class. To demonstrate, I will extend the sine wave generating code I created recently, to save the sine wave you are playing to disk. Only three extra lines of code are required:
IWavePlayer waveOut;
WaveRecorder recorder; 

private void button1_Click(object sender, EventArgs e)
{
    StartStopSineWave();
}

void StartStopSineWave()
{
    if (waveOut == null)
    {
        var sineWaveProvider = new SineWaveProvider16();
        sineWaveProvider.SetWaveFormat(16000, 1); // 16kHz mono
        sineWaveProvider.Frequency = 500;
        sineWaveProvider.Amplitude = 0.1f;
        recorder = new WaveRecorder(sineWaveProvider, @"C:\Users\Mark\Documents\sine.wav");
        waveOut = new WaveOut();
        waveOut.Init(recorder);
        waveOut.Play();
    }
    else
    {
        waveOut.Stop();
        waveOut.Dispose();
        waveOut = null;
        recorder.Dispose();
        recorder = null;                
    }
}

24 comments:

Unknown said...

I have a problem with this.
"LineOutRecorder.WaveRecorder' does not implement interface member 'NAudio.Wave.IWaveProvider.Read(NAudio.Wave.IWaveBuffer)"
Source:
http://www.paste.lt/paste/879522bf8c12bb7e6c6a7ab7eaad9d22
Sollution explorer screenshot:
http://i647.photobucket.com/albums/uu198/n2oo/sollution.png

Unknown said...

hi n20,
have you got the very latest NAudio source code out from Codeplex?

Unknown said...
This comment has been removed by the author.
Unknown said...

Version that I tried I downloaded today from Codeplex.

Unknown said...

You need to go to the source code tab and download the latest and build it. NAudio 1.3 has not yet been released yet.

Here's the code for the latest IWaveProvider:
http://naudio.codeplex.com/sourcecontrol/changeset/view/28979?projectName=naudio#258022

Unknown said...

Hi Mark,

I would like to record only the audio from one single application that plays music. is that possible with the NAUDIO lib ?

thanks!

Jelle

Unknown said...
This comment has been removed by the author.
Unknown said...

Thank You for replay Mark,
Here I found nice recording application example:
http://opensebj.blogspot.com/2009/04/naudio-tutorial-5-recording-audio.html

Is it possible to programmicaly select the recording device? (choose between mic, line-in and wave etc.)

Unknown said...

BTW I am using XP, seems that newest NAudio Demo has that function but as exeption said "This functionality is only supported on Windows Vista or newer."

One more question about recorder buffer, it contais bytes, so each audio sample is 8bit or divided into several array elements?

Unknown said...

hi n2o, you can't use WASAPI capture on XP. Use WaveIn instead. See my voice recorder article on the Coding4Fun website to learn more about recording audio in NAudio.

http://blogs.msdn.com/coding4fun/archive/2009/10/08/9905168.aspx

JC said...

Hi Mark! I'm on Windows 7 and am struggling to get NAudio to record the stereo mix - is this possible, maybe using WASAPI as opposed to WaveIn? I'm not sure how to do this with NAudio if it is.

I've used the tutorial example 5 from Codeplex and clicked the "Record all output from my soundcard" button, but it only records what is coming in from the microphone. Any pointers would be greatly appreciated!! Many thanks.

Unknown said...

hi JC, it probably depends on your sound-card drivers whether any of the WaveIn options can do this programatically. You might need to get the windows mixer up to select your recording source to make it work.

JIbin said...

HI,
Now i am able to capture sound and at the same time i send it over network like this



private void waveIn_DataAvailable(object sender, WaveInEventArgs e)
{
byte[] buffer = e.Buffer;

byte[] dataToWrite = ALawEncoder.ALawEncode(buffer);

if (socket_Audio != null)
socket_Audio.SendTo(dataToWrite, new IPEndPoint(IPAddress.Parse(RemoteIpv6), SoundPort));

}



Now the problem that i am facing is how to get back this audio at receiving end and and send it to the speaker of the system

I am just starting a new thread for rxving data from network

myAudioThread = new Thread(new ThreadStart(AudioListener));
myAudioThread.Start();

And in the AudioListener() i use the following code

#region for Audio socket Its rxving audio always

try
{

socket_Audio = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);

socket_Audio.Bind(new IPEndPoint(IPAddress.IPv6Any, SoundPort));
IPEndPoint remoteEP = new IPEndPoint(IPAddress.IPv6Any, SoundPort);
//Receive data.

byte[] byteData;
while (true)
{

byteData = new byte[2048];
//Receive data.
socket_Audio.Receive(byteData);

//G711 compresses the data by 50%, so we allocate a buffer of double
//the size to store the decompressed data.
byte[] byteDecodedData = new byte[byteData.Length * 2];

//Decompress data using the proper vocoder.

ALawDecoder.ALawDecode(byteData, out byteDecodedData);



}

Now the decoded audio is in the byte array byteDecodedData.How i send it to speaker of that system.

I found only waveOut.Play() and

public override int Read(byte[] buffer, int offset, int count)
{

//code to process data

}

in the examples

Pls guide me to the next step....Pls let me know if i am in wrong path.

Thanks

JIbin

jibin.mn@hotmail.com

Unknown said...

@Jlbin - have a look at the BufferedWaveProvider in the latest code. This can be used to buffer up your data read from the network and can be used to feed the soundcard

Seingalt said...

Hi Mark,

Great lib by the way.

I just want to ask if you have any ideas about recording conversations?

Is it possible to capture it using WASAPI or, in my case since I'm using win XP, WaveIn? Or do you know any alternatives to that?

seemenomore1987@gmail.com

Unknown said...

@Seingalt what do you mean by conversations? conversations on what program? if you mean skype, then you could look into using the Skype4Com object

Seingalt said...

Hi Mark,

I'd take a look at skype4com objects.

Basically, I have a web application that should allow the user to call (for example a Call Center Rep.) from that page and record that conversation.

I have done some research on skype4com already but somehow I'm still having a hard time on how I could use it on my application.

If you know or have any articles about it just post it here. Like "Using skype4com objects in your web application for biginners"

Thanks!

Unknown said...

@Seingalt - check my Coding4Fun article and project - SkypeFx

Anonymous said...

Thanks for the great example. I already noticed it at NAudio but since it was not actually used in tehir code, the sample was very helpful.

One question: I would like to use the WaveRecorder combined with the ASIO4All. The NADIO Asio only has AsioOut.cs. Is there a sample of how the Wave Recorder is used for ASIO?

Thanks
HM

Unknown said...

sadly NAudio ASIO support is very basic and doesn't work with all soundcards at the moment

Anonymous said...

Thanks Mark. I'm still hopefull, and struggeling with adding the ASIO-in interface by myself. I'm trying to figure out where to fit the WaveRecorder class.
Since WaveRecorder is actually an IWaveProvider, I plan to add AsioIn.cs which has "recordStream" of class WaveRecord (similar to AsioOut). I'm jyust confused wehther or not I need to add IWaveRecorder, similar to IWavePlayer.

Thanks you very much!
HM

Unknown said...

@HM, an AsioIn class should implement IWaveIn

Anonymous said...

Can I use that source code on windows mobile? Or do you have a code that record a wave file format on windows mobile? (records in a wave file format ..)

Anonymous said...

I want to change the frequency of this sinus wave without interrupting it and quite quickly . You said in your previous posts that I need to do it smoothly , how can I do that ? Sorry but I'm a beginner