0

I am working on a small project (just for fun) that allows me to display the frequencies played back in my current audio buffer.

The plan is:

The thread that is executing the WaveOutPlayer.ThreadProc() method keeps playing and refilling two audio buffers. While the first buffer is playing, the second buffer gets refilled and vice versa. To be sure that one buffer does not start playing before the other one stopped playback, I use the WaitOne() method. After the buffer's audio data has beed played back by the WaveOut device, the buffer's OnCompleted() method is called. This method cancels the waiting.

The playback itself starts, but it is very choppy.

Could anyone tell me why this happens?

The Visual Studio project is located at http://www.pour-toujours.net/lowlevelaudio.zip

(currently, only 16-bit, Stereo, 44,1kHz uncompressed wav is supported.)

Here goes some of my code that seems to be relevant:

 using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.Collections;
using System.Linq;

namespace LowLevelAudio
{
    internal class WaveOutHelper
    {
        public static void Try(int err)
        {
            if (err != WaveNative.MMSYSERR_NOERROR)
                throw new Exception(err.ToString());
        }
    }

    public delegate void BufferFillEventHandler(IntPtr data, int size);

    public class WaveOutBuffer : IDisposable
    {
        private AutoResetEvent m_PlayEvent = new AutoResetEvent(false);
        private IntPtr m_WaveOut;
        private int m_buffersize;
        private static byte[] m_samples;
        private static double[] m_fftsamples;
        private WaveNative.WaveHdr m_Header;
        private byte[] m_HeaderData;
        private GCHandle m_HeaderHandle;
        private GCHandle m_HeaderDataHandle;
        private WaveFormat m_waveformat;
        private double[] m_fftOccurances;
        private double[] m_fftHertzlist;
        private bool m_Playing;

        public int ID
        {
            get; set;
        }



        internal static void WaveOutProc(IntPtr hdrvr, int uMsg, int dwUser, ref WaveNative.WaveHdr wavhdr, int dwParam2)
        {
            if (uMsg == WaveNative.MM_WOM_DONE)
            {
                try
                {
                    GCHandle h = (GCHandle)wavhdr.dwUser;
                    WaveOutBuffer buf = (WaveOutBuffer)h.Target;
                    buf.OnPlayCompleted();
                }
                catch(Exception ex)
                {
                    Console.WriteLine("Exception: " + ex.Message);
                }
            }
        }

        public WaveOutBuffer(IntPtr waveOutHandle, int size, WaveFormat format)
        {
            m_WaveOut = waveOutHandle;
            m_waveformat = format;
            m_HeaderHandle = GCHandle.Alloc(m_Header, GCHandleType.Pinned);
            m_Header.dwUser = (IntPtr)GCHandle.Alloc(this);
            m_HeaderData = new byte[size];
            m_HeaderDataHandle = GCHandle.Alloc(m_HeaderData, GCHandleType.Pinned);
            m_Header.lpData = m_HeaderDataHandle.AddrOfPinnedObject();
            m_Header.dwBufferLength = size;
            m_buffersize = size;
            m_samples = new byte[m_buffersize];
            WaveOutHelper.Try(WaveNative.waveOutPrepareHeader(m_WaveOut, ref m_Header, Marshal.SizeOf(m_Header)));
        }
        ~WaveOutBuffer()
        {
            Dispose();
        }

        public int Size
        {
            get { return m_Header.dwBufferLength; }
        }

        public IntPtr Data
        {
            get { return m_Header.lpData; }
        }

        public void Dispose()
        {
            if (m_Header.lpData != IntPtr.Zero)
            {
                WaveNative.waveOutUnprepareHeader(m_WaveOut, ref m_Header, Marshal.SizeOf(m_Header));
                m_HeaderHandle.Free();
                m_Header.lpData = IntPtr.Zero;
            }
            m_PlayEvent.Close();
            if (m_HeaderDataHandle.IsAllocated)
                m_HeaderDataHandle.Free();
        }

        public bool Play()
        {
            lock(this) // works, but has to be fine tuned... (to do)
            {
                m_PlayEvent.Reset();
                m_Playing = WaveNative.waveOutWrite(m_WaveOut, ref m_Header, Marshal.SizeOf(m_Header)) == WaveNative.MMSYSERR_NOERROR;
                if (!m_Playing)
                    throw new Exception("test exception");
                return m_Playing;
            }
        }

        public void WaitForMe()
        {
            Console.WriteLine(this.ID + " WaitFor()");
            if (m_Playing)
            {
                m_Playing = m_PlayEvent.WaitOne();
            }
            else
            {
                m_Playing = false;
            }
        }

        public void OnPlayCompleted()
        {
            Console.WriteLine(this.ID + " OnCompleted()");
            m_PlayEvent.Set();
            m_Playing = false;
        }
    }

    public class WaveOutPlayer : IDisposable
    {
        private IntPtr m_WaveOut;
        private WaveOutBuffer[] m_bufferlist;
        private Thread m_Thread;
        private BufferFillEventHandler m_FillProc;
        private bool m_Finished;
        private byte m_zero;
        private int m_buffercount = 0;
        private int m_buffersize = 0;
        private WaveFormat m_waveformat; 

        private WaveNative.WaveDelegate m_BufferProc = new WaveNative.WaveDelegate(WaveOutBuffer.WaveOutProc);

        public static int DeviceCount
        {
            get { return WaveNative.waveOutGetNumDevs(); }
        }

        public WaveOutPlayer(int device, WaveFormat format, int bufferSize, BufferFillEventHandler fillProc)
        {
            m_zero = format.wBitsPerSample == 8 ? (byte)128 : (byte)0;
            m_FillProc = fillProc;
            m_buffercount = 2;
            m_buffersize = bufferSize;
            m_waveformat = format;
            WaveOutHelper.Try(WaveNative.waveOutOpen(out m_WaveOut, device, format, m_BufferProc, 0, WaveNative.CALLBACK_FUNCTION));
            AllocateBuffers(bufferSize, m_buffercount, format);
            m_Thread = new Thread(new ThreadStart(ThreadProc));
            m_Thread.Start();
        }

        ~WaveOutPlayer()
        {
            Dispose();
        }

        public void Dispose()
        {
            if (m_Thread != null)
            {
                try
                {
                    m_Finished = true;
                    if (m_WaveOut != IntPtr.Zero)
                        WaveNative.waveOutReset(m_WaveOut);
                    m_Thread.Join();
                    m_FillProc = null;
                    FreeBuffers();
                    if (m_WaveOut != IntPtr.Zero)
                        WaveNative.waveOutClose(m_WaveOut);
                }
                finally
                {
                    m_Thread = null;
                    m_WaveOut = IntPtr.Zero;
                }
            }
        }

        private void ThreadProc()
        {
            WaveOutBuffer b0 = m_bufferlist[0];
            WaveOutBuffer b1 = m_bufferlist[1];
            MainForm form = Program.getUI();

            bool s = true;
            m_FillProc(b0.Data, b0.Size);

            while (!m_Finished)
            {
                if (s)
                {
                    Console.WriteLine("-------------------------");
                    Console.WriteLine("Playing b0, filling b1");
                    b0.Play();
                    m_FillProc(b1.Data, b1.Size);

                    form.paintEqualizer(b0);
                    Console.WriteLine("callng waitFor on b0");
                    b0.WaitForMe();
                }
                else
                {
                    Console.WriteLine("-------------------------");
                    Console.WriteLine("Playing b1, filling b0");
                    b1.Play();
                    m_FillProc(b0.Data, b0.Size);

                    form.paintEqualizer(b1);
                    Console.WriteLine("callng waitFor on b1");
                    b1.WaitForMe();

                }
                s = !s;
            }
        }

        private void AllocateBuffers(int bufferSize, int bufferCount, WaveFormat format)
        {
            FreeBuffers();
            m_bufferlist = new WaveOutBuffer[m_buffercount];
            if (bufferCount > 0)
            {
                for (int i = 0; i < m_buffercount; i++)
                {
                    m_bufferlist[i] = new WaveOutBuffer(m_WaveOut, bufferSize, format);
                    m_bufferlist[i].ID = i;
                }
            }
        }

        private void FreeBuffers()
        {
            if (m_bufferlist != null)
            {
                foreach (WaveOutBuffer currentBuffer in m_bufferlist)
                {
                    if (currentBuffer != null)
                    {
                        currentBuffer.Dispose();
                    }
                }
            }
        }
    }
}
AudioGuy
  • 413
  • 5
  • 18
  • post the relevant code with your question. No one wants to download a zip file. – jaket Oct 06 '15 at 17:15
  • ah ok. sorry. I updated it. – AudioGuy Oct 06 '15 at 18:24
  • How big are the buffers? – jaket Oct 07 '15 at 16:56
  • I tried some sizes, from 8192 up to 65536 samples. Still choppy. – AudioGuy Oct 07 '15 at 17:26
  • Try commenting out the `form.paintEqualizer` lines. Also, you should probably be unpreparing the header after the callback and then repreparing it before it is reused. – jaket Oct 07 '15 at 18:50
  • oh alright, I will give it a try! I will get back here and tell you if it worked. – AudioGuy Oct 07 '15 at 19:33
  • ok, seems like there is no direct answer to this problem. found an ugly way around it, though: the whole AutoResetEvent thing did not work out, so I threw it all out. I also put the unpreparing/preparing thing into it - as you had suggested. Instead of just refilling, I created a new instance of WaveOutBuffer each time the old buffer was done with playing back the audio signal. Now it works. But I do not completely understand why. The WaveOut pipe should get filled too fast now because there is no real waiting going on. Maybe writing to the WaveOut blocks the thread? Don't know. Feels ugly. – AudioGuy Oct 10 '15 at 16:51
  • The api will let you queue up as many buffers as you want. It never blocks. The obvious downsides are that you can use a lot of memory and that the latency grows. Not sure why the events didn't work out for you. – jaket Oct 10 '15 at 18:05

0 Answers0