This project has moved. For the latest updates, please go here.

Caching WaveStreams

Nov 26, 2010 at 1:44 PM

Hi all,

what is the fastest method to clone WaveStream-Objects?

 

Something like this?

        private WaveStream CopyWaveStream(WaveStream input)
        {
                int bufferLength = (int)input.Length;
                byte[] streamBuffer = new byte[bufferLength];
                input.Read(streamBuffer, 0, bufferLength);
                MemoryStream newMemStream = new MemoryStream();
                var fileWriter = new WaveFileWriter(newMemStream, input.WaveFormat);
                fileWriter.WriteData(streamBuffer, 0, bufferLength);
                WaveFileReader reader = new WaveFileReader(newMemStream);
                return reader;
        }

Or exists simpler way with any BufferClass?

 

Background:

-I have to play 20 WaveStreams each one over 100 times with a short reaction time.

-And sometimes the same WaveStream can play many times at the same time.

-So i do the conversion-work only on first load and cache this WaveStream.

-I need the same wavestream-data with different play-positions at the same time...this means: i have to clone the CachedWaveStream with a minimum effort for each instance

I know this is a complex-scenario...lets focus in this discussion only on the fast cloning of WaveStreams. Thanks :)

Coordinator
Nov 26, 2010 at 4:27 PM

this will work, although the extra overhead of WaveFileReader and Writer is probably unnecessary. Have a look at the RawWaveSourceStream I recently checked in to NAudio. That lets you provide a MemoryStream and WaveFormat, meaning you would only need to clone the MemoryStream.

Even better would be to create your own WaveStream based on a byteArray full of raw audio, that kept track of its own position when Read was called. That way you don't waste memory copying the byte array as you could create multiple instances of that WaveStream all based off the same byte array.

Mark

Nov 28, 2010 at 1:11 PM

Hi Mark,

for my project i found a little issue in the WaveFileWriter.Dispose-method, if you want to use the stream-out version.

using (WaveFileWriter writer = new WaveFileWriter(outputStream, myWaveFormat))
{
     writer.WriteData(wavArray, 0, wavArray.Length);
}
after that the stream is closed...but everyone who use this Method needs the stream. ^^
my change:
    public class WaveFileWriter : IDisposable
    {
        ...
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (outStream != null)
                {
                    try
                    {
                        if (!overwriting)
                        {
                            UpdateHeader(writer);
                        }
                    }
                    finally
                    {
                        // in a finally block as we don't want the FileStream to run its disposer in
                        // the GC thread if the code above caused an IOException (e.g. due to disk full)
                        
                        if (toStream)
                        {
                            //for stream output
                            outStream.Position = 0; 
                        }
                        else
                        {

                            //for file output
                            outStream.Close(); // will close the underlying base stream
                            outStream = null;
                        }
                    }
                }
            }
        }
...
       }
i'll think this will be usefull for others too.
Coordinator
Nov 29, 2010 at 8:42 AM

yes, it is hard to know whether to dispose source streams when their consumer is disposed. BinaryWriter in the .NET framework does this for instance.

I usually work around this myself by wrapping the input stream with a simple IgnoreDisposeStream class.

Mark

Nov 29, 2010 at 11:35 AM
Edited Nov 29, 2010 at 11:35 AM

The problem in this case is: If you ignoring Dispose, the function "UpdateHeader" is not executed.

When the resulting stream (without dispose!) has in incomplete header, i run into misterious problems for the next module that expects a "perfect" wav-memorystream.

Coordinator
Nov 29, 2010 at 11:59 AM

yes you still call Dispose on the WaveFileWriter, but your derived output stream doesn't pass it on to the underlying MemoryStream. Have a look at IgnoreDisposeStream.cs in the WpfDemo

http://naudio.codeplex.com/SourceControl/changeset/view/64392#565848

Mark

Nov 29, 2010 at 12:25 PM

ok, this could be a very elegant way to handle this issue.

But the most users think that "WaveFileWriter(outputStream, waveFormat)" writes data into a valid stream. But by default the result is a closed stream (with dispose) or a incomplete stream (without dispose).

You must have the knowing of this issue to think about solutions like IgnoreDisposeStream...and a incomplete wav-stream with 2 wrong int-values is not very obvious (i spend 2 hours of my life for this!).

I would prefer to solve issues instead of a elegant way to handle it. :)

Coordinator
Nov 29, 2010 at 12:47 PM

the reason it is like this is that in NAudio it is very common to build up a long chain of WaveStreams (i.e. WaveStreams that take one or more input streams into their constructor). If you don't propagate dispose, then the use has to manually keep track of every stream in the graph to call dispose on it. I appreciate that those who are writing to MemoryStreams don't want dispose to be called. The constructor overload was not originally written with that use in mind. If I make the change, there will be some breakage to existing code, so I would have to fix that up at the same time. It is certainly a change I have considered making from time to time. Another option is to make UpdateHeader public, allowing you to simply not dispose the WaveFileWriter at all. Having said that I'll probably end up going with the approach I took in WaveFileReader, which is basically what you are asking for.

Mark

Nov 29, 2010 at 1:17 PM

This is a good argument...i didnt thought about this breaking change.

A public UpdateHeader (and/or this discussion here) could help others to prevent running in the same problem.