Sunday, April 05, 2009

NAudio Tutorial 5 - Recording Audio

Time for another installment of the NAudio Tutorials; this week we will be looking at how to Record Audio using NAudio from two different recording scenarios. The first being the use of NAudio to record any and all sound coming from the local Sound Card input, whether that be from a Microphone, the Line In Device or the Sound Cards on board wave mixer. The second approach we will be looking at is recording only the Audio that has been mixed by NAudio, regardless of what other audio is being played on the system at the time. This is useful for scenarios where you want to play over a backing track, or play your samples against a click track being played from another program but don't want to record the click track. The additional advantage of recording the audio mixed directly from NAudio is that there is 0 degradation in quality through the process; no audio playing means pure silence, rather than almost silence which for your average audio hardware would be the result, there is always some level of noise when working with an analog signal.

This NAudio Recording audio tutorial builds upon the concepts presented in previous NAudio Tutorials, if you haven't yet had the opportunity to review them I suggest that you venture there first and resume reading this tutorial after you have understood the basic NAudio concepts.

http://opensebj.blogspot.com/2009/02/introduction-to-using-naudio.html
http://opensebj.blogspot.com/2009/02/naudio-tutorial-2-mixing-multiple-wave.html
http://opensebj.blogspot.com/2009/03/naudio-tutorial-3-sample-properties.html
http://opensebj.blogspot.com/2009/03/naudio-tutorials-minor-note.html
http://opensebj.blogspot.com/2009/03/naudio-tutorial-4-sample-reversing.html

Time for another disclaimer, the second approach discussed here, recording the mix directly from NAudio has been suggested as a feature for inclusion in the main branch. I'm not sure if it fits in to the long term direction for the WaveMixerStream32 class, in any case, the code for these modifications have been included in this Tutorial and thanks to the Open Source nature of NAudio you can make these same changes to an instance of the library for yourself. You can find the specific details of this suggest contained in this forum post:

http://naudio.codeplex.com/Thread/View.aspx?ThreadId=52296

If you have any feedback on this tutorial, drop me a line or post a question in the comments section below.

Download the full article (AbiWord and RTF Format), example C#.Net Source Code and tutorial program here.

Recoding from the Sound Card

This is remarkably simple to achieve in NAudio, short of having a big red button which we push before it leaves the factory. First step is to setup.. ah forget the steps here is the code:

// WaveIn Streams for recording
WaveIn waveInStream;
WaveFileWriter writer;

waveInStream = new WaveIn(44100,2);
writer = new WaveFileWriter(outputFilename, waveInStream.WaveFormat);

waveInStream.DataAvailable += new EventHandler<WaveInEventArgs>(waveInStream_DataAvailable);
waveInStream.StartRecording();

No joke that's almost it. The only interesting thing here that we need to consider it that we have added an EventHandler that needs to be setup to handle data when it's ready to be handed off to the WaveFileWriter:

void waveInStream_DataAvailable(object sender, WaveInEventArgs e)
{
   writer.WriteData(e.Buffer, 0, e.BytesRecorded);
}

Er, thats it to start recording. We can stop the recording as such:

waveInStream.StopRecording();
waveInStream.Dispose();
waveInStream = null;
writer.Close();
writer = null;

See it would have been to simple a tutorial if we stopped here but feel free to stop reading and give it a crack. Using this method any audio which isn't muted on you input mixer will be recorded; it's up to you and the windows Mixer API to decide what you want to record, isn't that nice; except that you can't only record Audio from your audio application if there are other applications playing sounds in the background - say you get a call on your VOIP connection right in the middle of the hottest composition ever, or some one PM's you in IRC, or you click around on your PC looking for that cool new sample to load, with all the button clicks and other useless sounds being saved in to your mixed composition - oh no. Lets now look at how this unfortunate situation can be avoided.

Direct-To-Disk recoding via the NAudio WaveMixerStream32 Class

Now this is slightly more complicated but much more fun and presents you with a superior audio recording (especially on lousy or average audio hard ware, say my PC for instance). We will cover the code which will be required within the calling application first and then secondly review the changes required within the NAudio library, just so we have some comparison in amount of effort required for both approaches.

mixer.StreamMixToDisk(outputFilename);
mixer.StartStreamingToDisk();

Assuming you already have the mixer defined, thats all that is required to start recording. We can pause the streaming to disk by:

mixer.PauseStreamingToDisk();

Or resume by:

mixer.ResumeStreamingToDisk();

Finally stopping by:

mixer.StopStreamingToDisk();

Easy enough but we should cover whats required for this to actually work right? So lets dive in to the modifications in the WaveMixerStream32.cs file and hack till our hearts are content. In the declaration section of the class we need to add the following:

// Declarations to support the streamToDisk recording methodology
private bool streamToDisk;
private string streamToDiskFileName;
WaveFileWriter writer;

Now we add in the methods that support our calls:

/// <summary>
/// Starts the Strem To Disk recording if a file name to save the stream to has been setup
/// </summary>
public void StartStreamingToDisk()
{
   if (streamToDiskFileName != "")
   {
       streamToDisk = true;
   }
}

/// <summary>
/// Pause's the stream to disk recording (No further blocks are written during the mixing)
/// </summary>
public void PauseStreamingToDisk()
{
   streamToDisk = false;
}

/// <summary>
/// Resume streaming to disk
/// </summary>
public void ResumeStreamingToDisk()
{
   streamToDisk = true;
}

/// <summary>
/// Stop the streaming to disk and clean up
/// </summary>
public void StopStreamingToDisk()
{
   streamToDisk = false;
   writer.Close();
}

/// <summary>
/// Setup the StreamMixToDisk file and initalise the WaveFileWriter
/// </summary>
/// <param name="FileName">FileName to save the mixed stream</param>
public void StreamMixToDisk(string FileName)
{
   streamToDiskFileName = FileName;
   writer = new WaveFileWriter(FileName, this.WaveFormat);
}

/// <summary>
/// Using the final set of data passed through in the overriden read method to also be passed to the WaveFileWriter
/// </summary>
/// <param name="buffer">Data to be written</param>
/// <param name="offset">The Offset, should be 0 as we are taking the mixed data to write and want it all</param>
/// <param name="count">The total count of all the mixed data in the buffer</param>
private void WriteMixStreamOut(byte[] buffer, int offset, int count)
{
   // Write the data to the file
   writer.WriteData(buffer, offset, count);
}

All thats left is the modification to the Read method to pass this data back to the WriteMixStream method. Rather than pasting in the whole read method, even though it may make it look like I've done some extra work, I'll just copy in the last 8 or so lines:

position += count;
// If streamToDisk has been enabled the mixed audio will be streamed directly to a wave file, so we need to send the data to the wave file writer
if (streamToDisk)
{
   WriteMixStreamOut(readBuffer, 0, count);
}
return count;
}

Having jammed the check for streaming out to disk, after the final calculation and before the method is exited gives us everything we need to stream to our file. So now we have two methods of recording audio data and you want to know what my favorite part is?

You can actually use both at the same time and get multi-track / multi-channel audio recording on the same machine with a fairly standard sound card!

I normally refrain from using exclamation points but I was actually quite excited when I tested this. It means that some one can be jamming along on say a C# Audio Synthesizer / Beat Box or composition tool like OpenSebJ while another person is singing vocals or playing in a guitar riff through line in. I guess if your really talented you could be doing both at the same time, perhaps signing to the jam is more ilkley - what ever it is it can actually work; you can record both sets of audio separately - because the NAudio Stream-To-Disk method is not actually using your sound card to save the mixed result. Cool, well I think so.

Download the example program and have a look for yourself.






Conclusion

As pre usual, I've packaged up a copy of the entire article, along with a copy of the example program and source for your consumption. For the modifications required to the NAudio library, I have also copied in to the zip the modified version of WaveMixerStream32.cs for your convenience. Let me know if you have any questions, comments or if your keen to contribute to a project like OpenSebJ.

Until next time, when we look at - well I have actually decided yet. There are two things which are on the list from Tutorial 3 however I don't think they are currently the items which are peaking my interest, so lets assume it will most likely be something from the list below:


  •  Adding Audio Effects to a Stream 

  •  Transposing the frequency of the stream being played back

  •  Using MIDI to trigger audio samples

  • Playing compressed Audio (MP3 & OGG)

  •  Or something else that takes my fancy, write to me and suggest what that may be.



If you haven't already; Download the full article (AbiWord and RTF Format), example C#.Net Source Code and tutorial program here.

25 comments:

Anonymous said...

Where is the WaveIn class in your example? I see that it it using AudioInterface but I don't know where AudioInterface is at. I apologize for the newbie question.

Anonymous said...

Oh right, nevermind. I looked back through the previous tutorials and realize that they are meant to be followed in order. Sorry.

Anonymous said...

How do you tell it what device to record from? That seems to be totally missing. What if you have multiple mics?

SoundtrackFan said...

Thanks a lot for the article, incredibly useful. Can I just check, there seems to be more than one version of the DLL? E.g. WaveIn only seems to work when linked to the DLL version 1.3.5 , and not version 1.2.144 which is the one supplied with the 'DLL Only' ZIP package.

Anonymous said...

Hi
I want to notify that the header(Specially "data") written by wavewriter is not written at correct position
It would be helpful if you could provide the source code for Naudio.dll

Anonymous said...

Is it possiable to use this library to create a real time audio input? if yes how is it dont?

thanks tristan

hossein said...

hi
could you please tell me how can i change wave Encoding in WaveFormat class, i want to record wav file with ALaw encoding

thanks

nathanroberton said...

Does NAudio currently support analyzing the pitch from a Microphone? Basically what I'm trying to do is the functionality in Rockband or SingStar (where you sing a song and the visual shows you how well you are doing in terms of being on Pitch).

OpenSebJ said...

@ SoundtrackFan; All of the modifications to that DLL are covered in this and previous tutorials. If you want to reapply those changes to the latest version of NAudio you can do that by following the same approach as presented in the tutorials.

@nathanroberton; Honestly not sure, haven't looked in to it myself. Probably best if you post that question on the NAudio discussion forum. http://naudio.codeplex.com/Thread/List.aspx

Becoz said...

Hi, I have used your example in my program, and its working great. However, I would like to implement a "record on sound detected" function. So is there any way to get the amplitude or threshold of the sound,and only record if it is over a certain limit? Need some guidance, as I am new to the sound stuff.

JC said...

I can't get the example to record *all* sound from everywhere - only from the microphone. Is this possible through NAudio?

Many thanks for the project.

Josh.

FBSC said...

Hi, thanks for your fantastic library.

I need to calculate the frequency of the sound I record from the input device... How can I do it? Have I to use something like a FFT? I tried to calculate the frequency by counting the times wich te sound "touches" zero, dividing by 2 and dividing another time by the sample rate, but doesn't work...

Anonymous said...

Very interesting tutorial, at least to me, as a newbee to audio... Here is the question: I am developing an app that uses SKYPE4COM and wonder how can I record the conversations, that is, both parties or even conference calls. Can I use the first (simple) method presented in your tutorial, or the second (complex) one - without knowing much about audio I sense that despite multiple voice sources - mine from the mic and conversation's partner (from sound card?) it is not a mixed combo and the first method shouls suffice... Can you confirm?

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

dancilhoney said...
This comment has been removed by a blog administrator.
Satyaprateek said...

Thanks, your tutorials are quite helpful. Regarding the recording code, i need to remove the stop button and stop the audio on its own after a fixed amount of time. how would i implement this

Anonymous said...

Ok you asked for a suggestion:
How about recording to disk with on the fly compression into something like OGG or MP3 ? In other words when I am recording - I do not want an 90MB wave file created at all not even in memory - I want something more manageable say 8-10MB compressed audio on disk. Can it be done ? Also I want to use a BAckgroundworker to do it with an example would be nice.

Anonymous said...

Can this method of streaming to disk be used for WaveIn from the soundcard. Perhaps I have a nice soundcard and it does a good job.
Or should the mixer be used always to get the different devices ..??

OpenSebJ said...

Some more interesting comments recently, unfortunately no means of contacting you about them.

I'll look in to the idea of the direct to MP3 stream to disk recording - it may be possible now.

ben said...

hello sebastian,
This is a great tutorial. However, I'm trying to integrate the recording and the waveform together, like in http://voicerecorder.codeplex.com/.
The voice recorder is done in WPF, but I'm trying to develop a WinForm app. I've been looking at the voice recorder code and I wonder can you actually know the steps? It would be great if you could come up with a tutorial on that(recording and displaying the waveform). Thanks a lot!

Alexey said...

Hello,

The example program records nothing. It saves to file, but when the file is playing there is no sound. Is the example program supposed to record sound from soundboard only if there is "What you hear" device?

Thanks.

Bram Osterhout said...

I'm trying to use the second part (stream to disk), but the Read() event isn't running, so my WaveFileWriter is receiving no data (the event isn't being called).

Richard said...

Some changes:

waveInStream = new WaveIn(44100,2);

to

WaveIn waveInStream = new WaveIn();
and then
waveInStream .WaveFormat = new WaveFormat(44100, 8, 1); //for example

also...
writer.WriteData(e.Buffer, 0, e.BytesRecorded); //Obsolete

to

writer.Write(e.Buffer, 0, e.BytesRecorded);

Anonymous said...

Hey, i'm now doing a program to play music, i would like to record what i am playing. How can i manage to record the output of the process ? or maybe the output of soundcard ?

thanks a lot !!!

my mail zazou370@hotmail.fr

TrungNEMO said...

If we use NAudio to build a recording system to record 4 channels from MAudio 1010LT sound card simultaneously , the recording will be 24 a day, 7 days a week, and every hour the recorded byte will be saved in a WAV file.

Could you anyone here help us about it? it is possible? and samples for us to refer to?

Thanks