NullReferenceException in WaveOutEvent

Oct 17, 2012 at 10:24 PM

I'm having trouble with WaveOutEvent. I'm not sure if it is a bug or just my code, so for now I'm just raising a Discussion, not an Issue.

I'm using WaveOutEvent in an ASP.Net web application. It's a simple application that plays a list of songs. When one song ends, WaveOutEvent throws a PlayBackStopped event. I catch that, dispose the WaveOutEvent object; then create a new WaveOutEvent object, filereader, etc. It usually works, but about 1 time in 5, I get a NullReferenceException in WaveOutEvent.cs. See the offending statement (in yellow hightlight) on the attached screen shot. NB - I can make it happen at will, just by putting a breakpoint in RaisePlaybackStoppedEvent(). So, just by hitting the breakpoint and continuing, I get the error; or about 1 time in 5, without the breakpoint, I get the error. I can't figure out which variable is triggering the null reference, and I will concede that I don't understand the lambda operator, but my hypothesis is that 'this' is from another thread, which sometimes goes null depending on which thread runs first.

Also, this all arose because I am trying to Dispose of the object and create a new one, for each song. When I keep the same object, the problem does not occur (but as Mark pointed out, it will leak memory).

If anyone can help me sort this out, I'd be grateful.

Jake

Class usage:

    Public Sub PlayFile(ByRef fileName As String)       ' play one file
        Dim fileExt As String
        waveOutDevice = New WaveOutEvent()
        fileExt = fileName.Substring(fileName.LastIndexOf("."c))
        Select Case fileExt.ToLower                     ' pick the appropriate file reader
            Case ".aif", ".aiff"
                fReader = New AiffFileReader(fileName)
            Case ".mp3"
                fReader = New Mp3FileReader(fileName)
            Case ".wma"
                fReader = New WMAFileReader(fileName)
            Case Else
                Debug.WriteLine("WebAudio: Invalid File Extension: " + fileExt)
                Return
        End Select
        mainOutputStream = New WaveChannel32(fReader)       ' tie the file reader to the output stream
        mainOutputStream.PadWithZeroes = False              ' need this to detect eof
        waveOutDevice.Init(mainOutputStream)                ' tie output stream to output device
        waveOutDevice.Play()
    End Sub

Exception Detail:

System.NullReferenceException was unhandled
  Message=Object reference not set to an instance of an object.
  Source=System.Web
  StackTrace:
       at System.Web.HttpApplication.ThreadContext.Enter(Boolean setImpersonationContext)
       at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
       at System.Web.AspNetSynchronizationContext.CallCallbackPossiblyUnderLock(SendOrPostCallback callback, Object state)
       at System.Web.AspNetSynchronizationContext.CallCallback(SendOrPostCallback callback, Object state)
       at System.Web.AspNetSynchronizationContext.Post(SendOrPostCallback callback, Object state)
       at NAudio.Wave.WaveOutEvent.RaisePlaybackStoppedEvent() in C:\Users\Gavin\Documents\Visual Studio Projects\NAudio\NAudio\Wave\WaveOutputs\WaveOutEvent.cs:line 280
       at NAudio.Wave.WaveOutEvent.PlaybackThread() in C:\Users\Gavin\Documents\Visual Studio Projects\NAudio\NAudio\Wave\WaveOutputs\WaveOutEvent.cs:line 130
       at NAudio.Wave.WaveOutEvent.b__0(Object state) in C:\Users\Gavin\Documents\Visual Studio Projects\NAudio\NAudio\Wave\WaveOutputs\WaveOutEvent.cs:line 94
       at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
       at System.Threading.ExecutionContext.runTryCode(Object userData)
       at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
       at System.Threading.ThreadPoolWorkQueue.Dispatch()
       at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
  InnerException: 


Screen shot:

Ooops - can't do attachments in Discussion.

Offending statement is line #280 in WaveOutEvent.cs

Breakpoint at line #271 in WaveOutEvent.cs

 

Coordinator
Oct 19, 2012 at 3:40 PM

can you copy the offending line, as I think the line numbers may not match my copy WaveOutEvent. What version of NAudio are you using?

Oct 19, 2012 at 5:24 PM
markheath wrote:

can you copy the offending line, as I think the line numbers may not match my copy WaveOutEvent. What version of NAudio are you using?

I have NAudio 1.5 from 12/18/2011.

As mentioned in another thread, I am still trying to work out how to get the latest sources; in one swoop. What is the recommended procedure?

Thanks,

Jake

Here is the code, from WaveOutEvent.cs, with the line #'s added. Line 271 is where I put a breakpoint, and 280 is the statement that crashes.

 

        private void RaisePlaybackStoppedEvent()
        {
271         EventHandler handler = PlaybackStopped;
            if (handler != null)
            {
                if (this.syncContext == null)
                {
                    handler(this, EventArgs.Empty);
                }
                else
                {
280                 this.syncContext.Post(state => handler(this, EventArgs.Empty), null);
                }
            }
        }

 

Coordinator
Oct 19, 2012 at 5:30 PM

Hmmm, I was expecting SyncContext to be null in your case. Not really much of a web developer, so it might be a question for StackOverflow. To download the latest source code, click "Source Code" above, and then "Download".

Oct 19, 2012 at 6:11 PM

I now have \naudio_d26bc035efa6\ which is slightly different. Now it is somewhat improved, but I can still cause a failure by inserting the break point, and I still get a 1 in 20 failure rate.

 

Coordinator
Oct 19, 2012 at 6:22 PM

can you try with a small modification to the code to not use the sync context at all (just do the code in the sync context == null branch of the if statement)

Oct 19, 2012 at 7:05 PM

That appears to work, viz:

        private void RaisePlaybackStoppedEvent(Exception e)
        {
            var handler = PlaybackStopped;
            if (handler != null)
            {
//                if (this.syncContext == null)
//                {
                    handler(this, new StoppedEventArgs(e));
//                }
//                else
//                {
//                    this.syncContext.Post(state => handler(this, new StoppedEventArgs(e)), null);
//                }
            }
        }

I've tried it over 30 passes with no errors. I'll leave it run all afternoon, but it looks good!

Coordinator
Oct 19, 2012 at 7:08 PM

good stuff. I had assumed syncContext would be null on web servers. It was just added as a convenient way to make WinForms and WPF work.

Mark

May 26, 2014 at 8:48 PM
markheath wrote:
good stuff. I had assumed syncContext would be null on web servers. It was just added as a convenient way to make WinForms and WPF work. Mark
Sorry to resurrect an old thread, but I had this problem come back when I updated to NAudio 1.7.
At first I put in the same fix that was mentioned in the previous post - but then I realized that was only going to work for my ASP application and probably wouldn't work for WinForms. So, here is the code I am using now:
        private void RaisePlaybackStoppedEvent(Exception e)
        {
            var handler = PlaybackStopped;
            if (handler != null)                                    // anyone to notify?
            {
                if ((this.syncContext == null) || (this.syncContext.GetType() != typeof(System.Windows.Forms.WindowsFormsSynchronizationContext)))
                {
                    handler(this, new StoppedEventArgs(e));
                }
                else
                {
                    this.syncContext.Post(state => handler(this, new StoppedEventArgs(e)), null);
                }
            }
        }
    }
It works for the case where syncContext is null, and when it is an ASP context, and should work same as previous for a Winforms context.

Please comment if you would like this moved to the Issues section.

Jake