NAudio Loopback Record (record what you hear through the speaker)

Posted by Blake on 7/26/2013
)

There have been times when I have needed to record what is coming through the speaker. In the past I had used a project I created called basic audio that is hosted over at codeplex. basic audio uses the windows api to record from a given device. the main requirement though is that the audio drivers are capable of recording “what you hear”. as windows versions increment this functionality seems to be disappearing from a lot of drivers. as i moved to windows 8 basic audio was no longer able to record for me because my audio drivers don’t support “what you hear” on windows 8. enter NAaudio.

NAudio is a project that is hosted at CodePlex and has been actively developed for over a decade now (at least it appears that way from some of the comment files included). In one of the recent versions the added the ability to record from the loopback and the cool thing is that it appears to work whether the sound card supports recording “what you hear” or not. The code was fairly straightforward but I decided to create a wrapper that would make my re-use of it only a few lines of code when I needed it in my hobby projects. I typically code in Visual Basic but I went ahead and wrote this as a C# class and compiled it into the NAudio assembly. Hope this helps or saves someone time. I should note also, portions of this are based off of one of the samples that was included with NAudio. I basically turned it into a reusable class (for how I use it).

C#

    /// <summary>
    /// A wrapper for the WasapiLoopbackCapture that will implement basic recording to a file that is overwrite only.
    /// </summary>
    public class LoopbackRecorder
    {
        private IWaveIn _waveIn;
        private WaveFileWriter _writer;
        private bool _isRecording = false;

        /// <summary>
        /// Constructor
        /// </summary>
        public LoopbackRecorder()
        {
        } 

        /// <summary>
        /// Starts the recording.
        /// </summary>
        /// <param name="fileName"></param>
        public void StartRecording(string fileName)
        {   
            // If we are currently record then go ahead and exit out.
            if (_isRecording == true)
            {
                return;
            }
            _fileName = fileName;
            _waveIn = new WasapiLoopbackCapture();
            _writer = new WaveFileWriter(fileName, _waveIn.WaveFormat);
            _waveIn.DataAvailable += OnDataAvailable;
            _waveIn.RecordingStopped += OnRecordingStopped;
            _waveIn.StartRecording();
            _isRecording = true;
        }

        /// <summary>
        /// Stops the recording
        /// </summary>
        public void StopRecording()
        {
            if (_waveIn == null)
            {
                return;
            }
            _waveIn.StopRecording();
        }

        /// <summary>
        /// Event handled when recording is stopped.  We will clean up open objects here that are required to be 
        /// closed and/or disposed of.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnRecordingStopped(object sender, StoppedEventArgs e)
        {
            // Writer Close() needs to come first otherwise NAudio will lock up.
            if (_writer != null)
            {
                _writer.Close();
                _writer = null;
            }
            if (_waveIn != null) 
            {
                _waveIn.Dispose();
                _waveIn = null;
            }
            _isRecording = false;
            if (e.Exception != null)
            {
                throw e.Exception;
            }
        } // end void OnRecordingStopped

        /// <summary>
        /// Event handled when data becomes available.  The data will be written out to disk at this point.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnDataAvailable(object sender, WaveInEventArgs e)
        {
            _writer.Write(e.Buffer, 0, e.BytesRecorded);
            //int secondsRecorded = (int)(_writer.Length / _writer.WaveFormat.AverageBytesPerSecond);
        } 

        private string _fileName = "";
        /// <summary>
        /// The name of the file that was set when StartRecording was called.  E.g. the current file being written to.
        /// </summary>
        public string FileName
        {
            get
            {
                return _fileName;
            }
        }
    } 

To test this, I created a project (just happened to be in Visual Basic) where I added a WinForm with two buttons on it. 3 lines of code are required with the wrapper. A class wide variable declaring the recorder. A start recording line and a stop recording line. Basically it looked something like this (The C# obviously will be almost identical):

VB.Net

    Private _recorder As New NAudio.LoopbackRecorder

    Private Sub btnStartRecording_Click(sender As Object, e As EventArgs) Handles btnStartRecording.Click
        _recorder.StartRecording("c:tempnaudio.wav")
    End Sub

    Private Sub btnStopRecording_Click(sender As Object, e As EventArgs) Handles btnStopRecording.Click
        _recorder.StopRecording()
    End Sub