- Main Page
- Classes
- Files

- File List

# C:/Users/Andreas Schiffler/Desktop/SVN/NewGamePhysics/NewGamePhysics/Utilities/NativeAudio.cs

    00001 ﻿#region File Description
    00002 //-----------------------------------------------------------------------------
    00003 // NativeAudio.cs
    00004 //-----------------------------------------------------------------------------
    00005 #endregion
    00006
    00007 namespace NewGamePhysics.Utilities
    00008 {
    00009     using System;
    00010     using System.Threading;
    00011     using System.Runtime.InteropServices;
    00012
    00016     [StructLayout(LayoutKind.Sequential)]
    00017     public class WaveFormat
    00018     {
    00022         public ushort formatTag;
    00023
    00027         public ushort channels;
    00028
    00032         public uint samplesPerSec;
    00033
    00037         public uint averageBytesPerSec;
    00038
    00042         public ushort blockAlignment;
    00043
    00047         public ushort bitsPerSample;
    00048
    00052         public short cbSize;
    00053
    00060         public WaveFormat(uint rate, ushort bits, ushort channels)
    00061         {
    00062             if (!(rate > 0))
    00063             {
    00064                 throw new ArgumentException("'rate' must be greater than >0", "rate");
    00065             }
    00066
    00067             if (!((bits == 8) || (bits == 16)))
    00068             {
    00069                 throw new ArgumentException("'bits' must be 8 or 16", "bits");
    00070             }
    00071
    00072             if (!((channels == 1) || (channels == 2)))
    00073             {
    00074                 throw new ArgumentException("'channels' must be 1 or 2", "channels");
    00075             }
    00076
    00077             this.formatTag = 1; // lock to PCM
    00078             this.channels = channels;
    00079             this.samplesPerSec = rate;
    00080             this.bitsPerSample = bits;
    00081             this.cbSize = 0;
    00082
    00083             this.blockAlignment = (ushort)(channels * (bits / 8));
    00084             this.averageBytesPerSec = samplesPerSec * blockAlignment;
    00085         }
    00086     }
    00087
    00091     public class NativeAudioInterface
    00092     {
    00096         private const int MMSYSERR_BASE = 0;
    00097
    00101         private enum MMSYSERR : int
    00102         {
    00103             NOERROR = 0,
    00104             ERROR = (MMSYSERR_BASE + 1),
    00105             BADDEVICEID = (MMSYSERR_BASE + 2),
    00106             NOTENABLED = (MMSYSERR_BASE + 3),
    00107             ALLOCATED = (MMSYSERR_BASE + 4),
    00108             INVALHANDLE = (MMSYSERR_BASE + 5),
    00109             NODRIVER = (MMSYSERR_BASE + 6),
    00110             NOMEM = (MMSYSERR_BASE + 7),
    00111             NOTSUPPORTED = (MMSYSERR_BASE + 8),
    00112             BADERRNUM = (MMSYSERR_BASE + 9),
    00113             INVALFLAG = (MMSYSERR_BASE + 10),
    00114             INVALPARAM = (MMSYSERR_BASE + 11),
    00115             HANDLEBUSY = (MMSYSERR_BASE + 12),
    00116             INVALIDALIAS = (MMSYSERR_BASE + 13),
    00117             BADDB = (MMSYSERR_BASE + 14),
    00118             KEYNOTFOUND = (MMSYSERR_BASE + 15),
    00119             READERROR = (MMSYSERR_BASE + 16),
    00120             WRITEERROR = (MMSYSERR_BASE + 17),
    00121             DELETEERROR = (MMSYSERR_BASE + 18),
    00122             VALNOTFOUND = (MMSYSERR_BASE + 19),
    00123             NODRIVERCB = (MMSYSERR_BASE + 20),
    00124             LASTERROR = (MMSYSERR_BASE + 20)
    00125         }
    00126
    00130         private const int WAVERR_BASE = 32;
    00131
    00135         private enum WAVERR : int
    00136         {
    00137             NONE = 0,
    00138             BADFORMAT = WAVERR_BASE + 0,
    00139             STILLPLAYING = WAVERR_BASE + 1,
    00140             UNPREPARED = WAVERR_BASE + 2,
    00141             SYNC = WAVERR_BASE + 3,
    00142             LASTERROR = WAVERR_BASE + 3
    00143         }
    00144
    00148         public const int MMSYSERR_NOERROR = 0;
    00149
    00153         public const int MM_WOM_OPEN = 0x3BB;
    00154
    00158         public const int MM_WOM_CLOSE = 0x3BC;
    00159
    00163         public const int MM_WOM_DONE = 0x3BD;
    00164
    00168         public const int CALLBACK_FUNCTION = 0x00030000;
    00169
    00178         public delegate void WaveDelegate(
    00179             IntPtr hdrvr,
    00180             int uMsg,
    00181             int dwUser,
    00182             ref WaveHeader wavhdr,
    00183             int dwParam2);
    00184
    00188         [StructLayout(LayoutKind.Sequential)] public struct WaveHeader
    00189         {
    00193             public IntPtr lpData;
    00194
    00198             public int dwBufferLength;
    00199
    00203             public int dwBytesRecorded;
    00204
    00208             public IntPtr dwUser;
    00209
    00213             public int dwFlags;
    00214
    00218             public int dwLoops;
    00219
    00223             public IntPtr lpNext;
    00224
    00228             public int reserved;
    00229         }
    00230
    00231         #region native_calls
    00235         private const string mmdll = "winmm.dll";
    00236
    00237         [DllImport(mmdll)]
    00238         public static extern int waveOutPrepareHeader(IntPtr hWaveOut, ref WaveHeader lpWaveOutHdr, int uSize);
    00239
    00240         [DllImport(mmdll)]
    00241         public static extern int waveOutUnprepareHeader(IntPtr hWaveOut, ref WaveHeader lpWaveOutHdr, int uSize);
    00242
    00243         [DllImport(mmdll)]
    00244         public static extern int waveOutWrite(IntPtr hWaveOut, ref WaveHeader lpWaveOutHdr, int uSize);
    00245
    00246         [DllImport(mmdll)]
    00247         public static extern int waveOutOpen(out IntPtr hWaveOut, int uDeviceID, WaveFormat lpFormat, WaveDelegate dwCallback, int dwInstance, int dwFlags);
    00248
    00249         [DllImport(mmdll)]
    00250         public static extern int waveOutReset(IntPtr hWaveOut);
    00251
    00252         [DllImport(mmdll)]
    00253         public static extern int waveOutClose(IntPtr hWaveOut);
    00254
    00255         #endregion
    00256     }
    00257
    00262     public delegate void BufferFillCallback(byte[] data);
    00263
    00267         internal class SampleBuffer : IDisposable
    00268         {
    00272         private const int BufferWaitTimeoutSeconds = 10;
    00273
    00277         private IntPtr waveData;
    00278
    00282         private AutoResetEvent bufferCompleted = new AutoResetEvent(false);
    00283
    00287         private NativeAudioInterface.WaveHeader header;
    00288
    00292         private GCHandle headerHandle;
    00293
    00297         private byte[] headerData;
    00298
    00302         private GCHandle headerDataHandle;
    00303
    00307         private bool isPlaying;
    00308
    00317         internal static void WaveOutProc(
    00318             IntPtr hdrvr,
    00319             int message,
    00320             int dwUser,
    00321             ref NativeAudioInterface.WaveHeader waveHeader,
    00322             int dwParam2)
    00323         {
    00324             if (message == NativeAudioInterface.MM_WOM_DONE)
    00325             {
    00326                 try
    00327                 {
    00328                     GCHandle h = (GCHandle)waveHeader.dwUser;
    00329                     SampleBuffer buffer = (SampleBuffer)h.Target;
    00330                     buffer.OnCompleted();
    00331                 }
    00332                 catch
    00333                 {
    00334                 }
    00335             }
    00336         }
    00337
    00343         public SampleBuffer(IntPtr waveOutHandle, int bufferSize)
    00344                 {
    00345             // Sample buffer
    00346             waveData = waveOutHandle;
    00347
    00348             // Create header
    00349             headerHandle = GCHandle.Alloc(header, GCHandleType.Pinned);
    00350             header.dwUser = (IntPtr)GCHandle.Alloc(this);
    00351
    00352             // Create sample buffer
    00353             headerData = new byte[bufferSize];
    00354             headerDataHandle = GCHandle.Alloc(headerData, GCHandleType.Pinned);
    00355             header.lpData = headerDataHandle.AddrOfPinnedObject();
    00356             header.dwBufferLength = bufferSize;
    00357
    00358             // Prepare sample buffer
    00359             int error = NativeAudioInterface.waveOutPrepareHeader(
    00360                 waveData,
    00361                 ref header,
    00362                 Marshal.SizeOf(header));
    00363             if (error != NativeAudioInterface.MMSYSERR_NOERROR)
    00364             {
    00365                 throw new ApplicationException("Could not prepare sample buffer.");
    00366             }
    00367                 }
    00368
    00372         ~SampleBuffer()
    00373         {
    00374             Dispose();
    00375         }
    00376
    00380         public void Dispose()
    00381         {
    00382             // Clean header
    00383             if (header.lpData != IntPtr.Zero)
    00384             {
    00385                 NativeAudioInterface.waveOutUnprepareHeader(waveData, ref header, Marshal.SizeOf(header));
    00386                 headerHandle.Free();
    00387                 header.lpData = IntPtr.Zero;
    00388             }
    00389
    00390             // Clean sample buffer
    00391             if (headerDataHandle.IsAllocated)
    00392             {
    00393                 headerDataHandle.Free();
    00394             }
    00395
    00396             // Clean event
    00397             bufferCompleted.Close();
    00398
    00399             GC.SuppressFinalize(this);
    00400         }
    00401
    00405         public int Size
    00406         {
    00407             get { return header.dwBufferLength; }
    00408         }
    00409
    00413         public IntPtr Data
    00414         {
    00415             get { return header.lpData; }
    00416         }
    00417
    00422         public bool Play()
    00423         {
    00424             lock(this)
    00425             {
    00426                 this.bufferCompleted.Reset();
    00427                 int result = NativeAudioInterface.waveOutWrite(
    00428                     waveData,
    00429                     ref header,
    00430                     Marshal.SizeOf(header));
    00431                 this.isPlaying = (result == NativeAudioInterface.MMSYSERR_NOERROR);
    00432             }
    00433
    00434             return this.isPlaying;
    00435         }
    00436
    00440         public void WaitForBuffer()
    00441         {
    00442             if (this.isPlaying)
    00443             {
    00444                 this.bufferCompleted.WaitOne(new TimeSpan(0, 0, BufferWaitTimeoutSeconds));
    00445             }
    00446         }
    00447
    00451         public void OnCompleted()
    00452         {
    00453             lock (this)
    00454             {
    00455                 this.bufferCompleted.Set();
    00456                 this.isPlaying = false;
    00457             }
    00458         }
    00459     }
    00460
    00464     public class WaveOutPlayer : IDisposable
    00465     {
    00469         private const int NumBuffers = 2;
    00470
    00474         private const int BufferSize = 4096;
    00475
    00479         private IntPtr waveOutHandle;
    00480
    00484         private SampleBuffer[] buffers = new SampleBuffer[NumBuffers];
    00485
    00489         private int currentBuffer;
    00490
    00494         private Thread fillBufferThread;
    00495
    00499         private BufferFillCallback bufferFillCallback;
    00500
    00504                 private bool finished;
    00505
    00509                 private byte zeroSampleValue;
    00510
    00514         private NativeAudioInterface.WaveDelegate waveOutProc =
    00515             new NativeAudioInterface.WaveDelegate(SampleBuffer.WaveOutProc);
    00516
    00522         public WaveOutPlayer(
    00523             WaveFormat format,
    00524             BufferFillCallback bufferFillCallback)
    00525         {
    00526             // Determine quiet sample value
    00527                         this.zeroSampleValue = format.bitsPerSample == 8 ? (byte)128 : (byte)0;
    00528
    00529             // Remember callback
    00530             this.bufferFillCallback = bufferFillCallback;
    00531
    00532             // Open wave out device.
    00533             int result = NativeAudioInterface.waveOutOpen(
    00534                 out waveOutHandle,
    00535                 -1,
    00536                 format,
    00537                 waveOutProc,
    00538                 0,
    00539                 NativeAudioInterface.CALLBACK_FUNCTION);
    00540             if (result != NativeAudioInterface.MMSYSERR_NOERROR)
    00541             {
    00542                 throw new ApplicationException("Could not open wave out device. Error: " + result);
    00543             }
    00544
    00545             // Allocate sample buffers.
    00546             AllocateBuffers();
    00547
    00548             // Start thread which provides sample data.
    00549             fillBufferThread = new Thread(new ThreadStart(WaveOutPlayerThread));
    00550             fillBufferThread.Start();
    00551         }
    00552
    00556         ~WaveOutPlayer()
    00557         {
    00558             Dispose();
    00559         }
    00560
    00564         public void Dispose()
    00565         {
    00566             if (fillBufferThread != null)
    00567             {
    00568                 try
    00569                 {
    00570                     finished = true;
    00571                     if (waveOutHandle != IntPtr.Zero)
    00572                     {
    00573                         NativeAudioInterface.waveOutReset(waveOutHandle);
    00574                     }
    00575
    00576                     fillBufferThread.Join();
    00577                     bufferFillCallback = null;
    00578                     FreeBuffers();
    00579                     if (waveOutHandle != IntPtr.Zero)
    00580                     {
    00581                         NativeAudioInterface.waveOutClose(waveOutHandle);
    00582                     }
    00583                 }
    00584                 finally
    00585                 {
    00586                     fillBufferThread = null;
    00587                     waveOutHandle = IntPtr.Zero;
    00588                 }
    00589             }
    00590
    00591             GC.SuppressFinalize(this);
    00592         }
    00593
    00597         private void WaveOutPlayerThread()
    00598         {
    00599             while (!finished)
    00600             {
    00601                 byte[] sampleData = new byte[BufferSize];
    00602
    00603                 if (bufferFillCallback != null)
    00604                 {
    00605                     // Fill samples via callback
    00606                     bufferFillCallback(sampleData);
    00607                 }
    00608                 else
    00609                 {
    00610                     // Create a buffer with zero sample values
    00611                     for (int i = 0; i < BufferSize; i++)
    00612                     {
    00613                         sampleData[i] = zeroSampleValue;
    00614                     }
    00615                 }
    00616
    00617                 // Wait for buffer to complete playing
    00618                 this.buffers[currentBuffer].WaitForBuffer();
    00619
    00620                 // Copy new sample data to unmanaged side
    00621                 System.Runtime.InteropServices.Marshal.Copy(
    00622                     sampleData, 0, buffers[currentBuffer].Data, buffers[currentBuffer].Size);
    00623
    00624                 // Trigger playback of buffer via wavOut device
    00625                 this.buffers[currentBuffer].Play();
    00626
    00627                 // Advance to next buffer to fill
    00628                 currentBuffer++;
    00629                 currentBuffer = currentBuffer % NumBuffers;
    00630             }
    00631
    00632             // Wait for all buffers to finish playing
    00633                         WaitForAllBuffers();
    00634                 }
    00635
    00639         private void AllocateBuffers()
    00640         {
    00641             FreeBuffers();
    00642             for (int i = 0; i < NumBuffers; i++)
    00643             {
    00644                 this.buffers[i] = new SampleBuffer(this.waveOutHandle, BufferSize);
    00645             }
    00646         }
    00647
    00651         private void FreeBuffers()
    00652         {
    00653             for (int i = 0; i < NumBuffers; i++)
    00654             {
    00655                 if (this.buffers[i] != null)
    00656                 {
    00657                     this.buffers[i].Dispose();
    00658                     this.buffers[i] = null;
    00659                 }
    00660             }
    00661         }
    00662
    00666         private void WaitForAllBuffers()
    00667         {
    00668             for (int i = 0; i < NumBuffers; i++)
    00669             {
    00670                 if (this.buffers[i] != null)
    00671                 {
    00672                     this.buffers[i].WaitForBuffer();
    00673                 }
    00674             }
    00675         }
    00676     }
    00677 }
    00678