tags:

views:

47

answers:

2

I am working on an app which records input from microphones to .wav files using NAudio. My Recorder class uses a BackgroundWorker to record. I start recording and keep doing so until either 10s have elapsed, or the user requested a cancellation. This works fine in most cases, except one. In debug, when I unplug the recording device midway through, things go nowhere. I expected that the problem would be caught in the BackgroundWorkedCompletedTask method by the case where the worker completion event args is e.Error (which works fine if I throw any standard exception), but this is not the case. When I select "Break All", it brings me to the BackgroundWorkedCompletedTask method, on the line this.waveIn.StopRecording();, and all properties on the object appear as Cannot evaluate expression because a native frame is on top of the call stack.

I assume this has to do with NAudio using un-managed code to interact with the device; what I would appreciate is any guidance or suggestions on how one should deal with situations like this. It doesn't seem like a try/catch block will do the job here, so where should I deal with it? Thanks in advance for any help.

For completeness, here are the 2 relevant parts of code involved:

private void RecordUsingBackgroundWorker(BackgroundWorker worker)
{
   var devices = this.FindDevices();
   var deviceIndex = devices.IndexOf(this.requestedDevice);
   var waveIn = new WaveIn(WaveCallbackInfo.FunctionCallback());
   waveIn.DeviceNumber = deviceIndex;
   waveIn.WaveFormat = new WaveFormat(8000, 1);
   this.waveIn = waveIn;

   this.waveIn.DataAvailable += new EventHandler<WaveInEventArgs>(DataIsAvailable);

   var filename = Globals.ThisAddIn.CommentFilePath;
   this.waveFileWriter = new WaveFileWriter(filename, waveIn.WaveFormat);

   var stopwatch = new Stopwatch();
   stopwatch.Start();

   this.waveIn.StartRecording();
   while (stopwatch.ElapsedMilliseconds < 10000 && !worker.CancellationPending)
   {
   }
}

Here is the code that executes when work is done:

private void BackgroundWorkedCompletedTask(object sender, RunWorkerCompletedEventArgs e)
{
   if (e.Cancelled)
   {
   }
   else if (e.Error != null)
   {
   }

   if (this.waveIn != null)
   {
      this.waveIn.StopRecording();
   }
   this.CleanUpAfterStoppedRecording();
   this.IsRecording = false;

   if (this.RecorderBusyRecordingChanged != null)
   {
      this.RecorderBusyRecordingChanged(this, new RecorderBusyEventArgs(false));
   }
}
+1  A: 

If the call to StopRecording is blocking, one option is to wrap it up in an asynchronous call and wait for it complete, with a timeout:

    public bool WaitFor(Action action, TimeSpan timeout)
    {
        var waitHandle = new AutoResetEvent(false);
        ThreadPool.QueueUserWorkItem(state =>
        {
            action();
            waitHandle.Set();
        });

        return waitHandle.WaitOne(timeout);
    }

You can use it like this:

        if (!WaitFor(this.waveIn.StopRecording, TimeSpan.FromSeconds(2)))
            throw new TimeoutException("Timed out waiting for recording to stop");

Note that this leaves the thread pool thread blocked on the action that timed out, so you'll either want to call an API method that cancels pending operations (if one exists) or you should proceed with shutting down your application politely (since you have a thread out in limbo and your application state may be compromised.) In a real production scenario, it would be good to improve this method a bit further so that you can get positive confirmation if the task ever did complete after the timeout period (useful if you have some form of cancellation you can invoke.)

Dan Bryant
+1  A: 

Hmya, I've seen problems with NAudio like this before. It uses mmsystem in a way that makes deadlock pretty common. Not so sure it deserves the blame, it uses the API in a documented way. Just a way that rarely is put to the test. Audio drivers are also a common source of trouble, the cut-throat competition doesn't seem to leave much money for a good software engineer.

Unplugging devices while they are in use is always a problem, works about as well as jerking a flash drive out of the USB slot while it is being written to. Or unplugging a USB serial port emulator while the port is open. The urge to unplug devices while they are used seems irresistible. Albeit that they'll get bored with it after trying it a few times.

Maybe Larry Osterman may have some input, he worked on the audio layer a lot. He posts here, you could leave a message at his blog to direct him to this question. Beyond that, you'll need to find a way to get the NAudio author to work together with Microsoft Support. Or tackle this yourself, source code is provided for a reason. Good luck.

Hans Passant