views:

399

answers:

1

I wrote a WinMM library wrapper library that exposes WaveOut and WaveIn classes for the purpose of recording and playing raw audio streams.

Everything works great, but in order to follow the operating system specs on how to handle the finished buffers, I added a thread that unprepares the buffers and frees the memory. I also got all of the synchronization down, so that the classes are solid and thread-safe.

However, there seems to be a rare issue where I add a buffer to the WaveOut device and the operating system returns a success code, but if the device is reset immediately afterwords, the OS does not mark the buffer as finished and return it to the application.

It seems that it is loosing the buffers. So, the problem is, I'm keeping track of the count of individual buffers sent to the device and blocking joining the Close() function to the thread that cleans them up.

Any ideas, or known bugs?

PS: This does NOT seem to happen on a Quad Core in Vista, but does happen on my Dual Core in XP pro.

EDIT1: I'm totally willing to expose the full source code, once I get it uploaded and propery licensed on codeplex, if that would help anybody.

EDIT2: Posted library to CodePlex: http://winmm.codeplex.com/

Here is an idea of what causes the issue:

public partial class MainView : Form
{
    private WaveIn waveIn = new WaveIn(WaveIn.WaveInMapperDeviceId);
    private WaveOut waveOut = new WaveOut(WaveOut.WaveOutMapperDeviceId);

    public MainView()
    {
        InitializeComponent();

        WaveFormat format = WaveFormat.Pcm44Khz16BitMono;

        waveOut.Open(format);

        waveIn.DataReady += new EventHandler<DataReadyEventArgs>(WaveIn_DataReady);

        // Tweaking these values affects the internal buffering thread.
        // Setting too small of a QueueSize with too small of a BufferSize
        //  will cause buffer underruns, which will sound like choppy audio.
        waveIn.BufferQueueSize = 200;
        waveIn.BufferSize = 64;

        waveIn.Open(format);

        waveIn.Start();
    }

    void WaveIn_DataReady(object sender, DataReadyEventArgs e)
    {
        if (waveOut != null)
        {
            lock (waveOut)
            {
                // We have to check for null after the lock,
                //  because waveOut may have been disposed
                //  inside another lock.
                if (waveOut != null)
                {
                    waveOut.Write(e.Data);
                }
            }
        }
    }

    private void MainView_FormClosed(object sender, FormClosedEventArgs e)
    {
        if (waveIn != null)
        {
            lock (waveIn)
            {
                waveIn.Dispose();
                waveIn = null;
            }
        }

        if (waveOut != null)
        {
            lock (waveOut)
            {
                waveOut.Dispose();
                waveOut = null;
            }
        }
    }
}
+1  A: 

I would first create a separate object on which to lock. That should simplify a lot of your null-checking logic (by about half since you check before and after the lock).

Second, Dispose will not set your variable to null, so your other checks will still pass through because the object is not null, only disposed. So I would do

waveOut.Dispose();
waveout = null;

to make sure that it is explictly set to null.

Erich Mirabal
Ah, good idea on the seperate object.Also, on the actual app that I was running into the issues on, I was explicetly setting it to null.
John Gietzen
Since you at least gave some constructive feedback, I'm going to grant you the bounty, even though I'm not sure if the issue is fixed or not.
John Gietzen