A possible memory issue in NAudio

Dec 10, 2012 at 1:45 AM

Background

I have a reasonably complex application where an NAudio WaveIn stream is started and seems to run correctly.  The input has two 44,100 channels.  But if additional windows in the application are opened the application falls over with an "Access violation on location 0x00000000".  These additional windows have nothing to do with NAudio code.  The OS is Windows 7.

On investigation it appears that the problem only occurs if the waveIn BufferMilliseconds value has been increased to 482 milliseconds or greater, giving a buffer over 85,000 bytes.  It is possible the crash may be connected to the garbage collector coming in soon after the additional, and memory hungry,  application windows open.

Possible Cause

Suspecting a memory issue I changed the following code in the NAudio file WaveInBuffer.cs so the 'header' value is now 'pinned'.  This seems to fix the problem.  

//hHeader = GCHandler.Alloc(header);

hHeader = GCHandler.Alloc(header, GCHandlerType.Pinned);

I am not certain of the exact difference between the GCHandlerTypes 'Normal' and 'Pinned' but it may be worthwhile making this change to NAudio. 

Kind Regards

John C

Dec 10, 2012 at 5:30 AM

Just a comment on getting access violations if BufferMilliseconds is 482 or higher.   I see that anything 85,000 bytes or larger is placed by the garbage collector on the large object heap so is treated differently by the garbage collector.

John C

 

 

Coordinator
Dec 10, 2012 at 7:15 AM

thanks for reporting this. Pinning is where you tell the garbage collector that it is not allowed to move the location of something. You need to pin if you pass a pointer to an API and it might access that pointer outside of the call (I'm pretty sure that the .NET marshalling pins things during the API call). However pinning reduces the efficiency of the GC as it has to work around the objects that cannot be moved, which is my I pin as little as I can get away with in NAudio.

This probably means that the same issue is possible with WaveOutBuffer as well, although I am a little surprised I have never encountered it. That is quite a high value for buffer milliseconds though, so that might explain it.

I'll try to get round to sorting this out soon. I'm a bit concerned that it could mean the use of the userData member is not safe (which would require pinning the entire WaveInBuffer class). It might be better to use a buffer index instead.

Mark

Coordinator
Dec 10, 2012 at 8:02 AM

thinking about it the userData ought to be safe, since the windows APIs don't actually use it and it is a GC Handle, not a pointer. I've made the change you suggest, so if you get the chance, please try again with the very latest version of the code.

Mark

Dec 11, 2012 at 1:55 AM

Mark,

I have tested nAudio with hHeader pinned and everything seems to work as expected.   As far as I can see the hHeader pinning is the only change needed, at least on the WaveIn side. 

It appears this problem only occurs if 1) the wave input buffer is over 85,000 bytes (at least 482 millisecond buffers with two 16 bit channels) and 2) the Garbage Collector is needing to be aggressive. 

In my case the application windows which were started at the time nAudio gave an access violation presented real time graphics on incoming analog data.  The input data analysis was displayed each second.  The processing involved creating a large number of short lifetime, but large objects.  From Perfmon the large object heap was growing into the hundreds of megabytes.   I know - the code will be changed to reuse the large objects rather than creating new ones. 

Thanks again Mark for a nice general purpose product in nAudio.

John C