/NAudio/Wave/WaveOutputs/WaveOutEvent.cs
# · C# · 374 lines · 265 code · 36 blank · 73 comment · 52 complexity · 0edd754399289556590243aac6b2b6b0 MD5 · raw file
- using System;
- using System.Diagnostics;
- using System.Threading;
- using System.Runtime.InteropServices;
- using System.Windows.Forms.VisualStyles;
-
- namespace NAudio.Wave
- {
- /// <summary>
- /// Alternative WaveOut class, making use of the Event callback
- /// </summary>
- public class WaveOutEvent : IWavePlayer, IWavePosition
- {
- private readonly object waveOutLock;
- private readonly SynchronizationContext syncContext;
- private IntPtr hWaveOut; // WaveOut handle
- private WaveOutBuffer[] buffers;
- private IWaveProvider waveStream;
- private volatile PlaybackState playbackState;
- private AutoResetEvent callbackEvent;
- private float volume = 1.0f;
-
- /// <summary>
- /// Indicates playback has stopped automatically
- /// </summary>
- public event EventHandler<StoppedEventArgs> PlaybackStopped;
-
- /// <summary>
- /// Gets or sets the desired latency in milliseconds
- /// Should be set before a call to Init
- /// </summary>
- public int DesiredLatency { get; set; }
-
- /// <summary>
- /// Gets or sets the number of buffers used
- /// Should be set before a call to Init
- /// </summary>
- public int NumberOfBuffers { get; set; }
-
- /// <summary>
- /// Gets or sets the device number
- /// Should be set before a call to Init
- /// This must be between 0 and <see>DeviceCount</see> - 1.
- /// </summary>
- public int DeviceNumber { get; set; }
-
- /// <summary>
- /// Opens a WaveOut device
- /// </summary>
- public WaveOutEvent()
- {
- syncContext = SynchronizationContext.Current;
- if (syncContext != null &&
- ((syncContext.GetType().Name == "LegacyAspNetSynchronizationContext") ||
- (syncContext.GetType().Name == "AspNetSynchronizationContext")))
- {
- syncContext = null;
- }
-
- // set default values up
- DeviceNumber = 0;
- DesiredLatency = 300;
- NumberOfBuffers = 2;
-
- this.waveOutLock = new object();
- }
-
- /// <summary>
- /// Initialises the WaveOut device
- /// </summary>
- /// <param name="waveProvider">WaveProvider to play</param>
- public void Init(IWaveProvider waveProvider)
- {
- if (playbackState != PlaybackState.Stopped)
- {
- throw new InvalidOperationException("Can't re-initialize during playback");
- }
- if (hWaveOut != IntPtr.Zero)
- {
- // normally we don't allow calling Init twice, but as experiment, see if we can clean up and go again
- // try to allow reuse of this waveOut device
- // n.b. risky if Playback thread has not exited
- DisposeBuffers();
- CloseWaveOut();
- }
-
- this.callbackEvent = new AutoResetEvent(false);
-
- this.waveStream = waveProvider;
- int bufferSize = waveProvider.WaveFormat.ConvertLatencyToByteSize((DesiredLatency + NumberOfBuffers - 1) / NumberOfBuffers);
-
- MmResult result;
- lock (waveOutLock)
- {
- result = WaveInterop.waveOutOpenWindow(out hWaveOut, (IntPtr)DeviceNumber, waveStream.WaveFormat, callbackEvent.SafeWaitHandle.DangerousGetHandle(), IntPtr.Zero, WaveInterop.WaveInOutOpenFlags.CallbackEvent);
- }
- MmException.Try(result, "waveOutOpen");
-
- buffers = new WaveOutBuffer[NumberOfBuffers];
- playbackState = PlaybackState.Stopped;
- for (int n = 0; n < NumberOfBuffers; n++)
- {
- buffers[n] = new WaveOutBuffer(hWaveOut, bufferSize, waveStream, waveOutLock);
- }
- }
-
- /// <summary>
- /// Start playing the audio from the WaveStream
- /// </summary>
- public void Play()
- {
- if (this.buffers == null || this.waveStream == null)
- {
- throw new InvalidOperationException("Must call Init first");
- }
- if (playbackState == PlaybackState.Stopped)
- {
- playbackState = PlaybackState.Playing;
- callbackEvent.Set(); // give the thread a kick
- ThreadPool.QueueUserWorkItem((state) => PlaybackThread(), null);
- }
- else if (playbackState == PlaybackState.Paused)
- {
- Resume();
- callbackEvent.Set(); // give the thread a kick
- }
- }
-
- private void PlaybackThread()
- {
- Exception exception = null;
- try
- {
- DoPlayback();
- }
- catch (Exception e)
- {
- exception = e;
- }
- finally
- {
- playbackState = PlaybackState.Stopped;
- // we're exiting our background thread
- RaisePlaybackStoppedEvent(exception);
- }
- }
-
- private void DoPlayback()
- {
- while (playbackState != PlaybackState.Stopped)
- {
- if (!callbackEvent.WaitOne(DesiredLatency))
- Debug.WriteLine("WARNING: WaveOutEvent callback event timeout");
-
- // requeue any buffers returned to us
- if (playbackState == PlaybackState.Playing)
- {
- int queued = 0;
- foreach (var buffer in buffers)
- {
- if (buffer.InQueue || buffer.OnDone())
- {
- queued++;
- }
- }
- if (queued == 0)
- {
- // we got to the end
- this.playbackState = PlaybackState.Stopped;
- callbackEvent.Set();
- }
- }
- }
- }
-
- /// <summary>
- /// Pause the audio
- /// </summary>
- public void Pause()
- {
- if (playbackState == PlaybackState.Playing)
- {
- MmResult result;
- lock (waveOutLock)
- {
- result = WaveInterop.waveOutPause(hWaveOut);
- }
- if (result != MmResult.NoError)
- {
- throw new MmException(result, "waveOutPause");
- }
- playbackState = PlaybackState.Paused;
- }
- }
-
- /// <summary>
- /// Resume playing after a pause from the same position
- /// </summary>
- private void Resume()
- {
- if (playbackState == PlaybackState.Paused)
- {
- MmResult result;
- lock (waveOutLock)
- {
- result = WaveInterop.waveOutRestart(hWaveOut);
- }
- if (result != MmResult.NoError)
- {
- throw new MmException(result, "waveOutRestart");
- }
- playbackState = PlaybackState.Playing;
- }
- }
-
- /// <summary>
- /// Stop and reset the WaveOut device
- /// </summary>
- public void Stop()
- {
- if (playbackState != PlaybackState.Stopped)
- {
- // in the call to waveOutReset with function callbacks
- // some drivers will block here until OnDone is called
- // for every buffer
- playbackState = PlaybackState.Stopped; // set this here to avoid a problem with some drivers whereby
- MmResult result;
- lock (waveOutLock)
- {
- result = WaveInterop.waveOutReset(hWaveOut);
- }
- if (result != MmResult.NoError)
- {
- throw new MmException(result, "waveOutReset");
- }
- callbackEvent.Set(); // give the thread a kick, make sure we exit
- }
- }
-
- /// <summary>
- /// Gets the current position in bytes from the wave output device.
- /// (n.b. this is not the same thing as the position within your reader
- /// stream - it calls directly into waveOutGetPosition)
- /// </summary>
- /// <returns>Position in bytes</returns>
- public long GetPosition()
- {
- lock (waveOutLock)
- {
- var mmTime = new MmTime();
- mmTime.wType = MmTime.TIME_BYTES; // request results in bytes, TODO: perhaps make this a little more flexible and support the other types?
- MmException.Try(WaveInterop.waveOutGetPosition(hWaveOut, out mmTime, Marshal.SizeOf(mmTime)), "waveOutGetPosition");
-
- if (mmTime.wType != MmTime.TIME_BYTES)
- throw new Exception(string.Format("waveOutGetPosition: wType -> Expected {0}, Received {1}", MmTime.TIME_BYTES, mmTime.wType));
-
- return mmTime.cb;
- }
- }
-
- /// <summary>
- /// Gets a <see cref="Wave.WaveFormat"/> instance indicating the format the hardware is using.
- /// </summary>
- public WaveFormat OutputWaveFormat
- {
- get { return this.waveStream.WaveFormat; }
- }
-
- /// <summary>
- /// Playback State
- /// </summary>
- public PlaybackState PlaybackState
- {
- get { return playbackState; }
- }
-
- /// <summary>
- /// Obsolete property
- /// </summary>
- [Obsolete]
- public float Volume
- {
- get { return volume; }
- set
- {
- WaveOut.SetWaveOutVolume(value, hWaveOut, waveOutLock);
- volume = value;
- }
- }
-
- #region Dispose Pattern
-
- /// <summary>
- /// Closes this WaveOut device
- /// </summary>
- public void Dispose()
- {
- GC.SuppressFinalize(this);
- Dispose(true);
- }
-
- /// <summary>
- /// Closes the WaveOut device and disposes of buffers
- /// </summary>
- /// <param name="disposing">True if called from <see>Dispose</see></param>
- protected void Dispose(bool disposing)
- {
- Stop();
-
- if (disposing)
- {
- DisposeBuffers();
- }
-
- CloseWaveOut();
- }
-
- private void CloseWaveOut()
- {
- if (callbackEvent != null)
- {
- callbackEvent.Close();
- callbackEvent = null;
- }
- lock (waveOutLock)
- {
- if (hWaveOut != IntPtr.Zero)
- {
- WaveInterop.waveOutClose(hWaveOut);
- hWaveOut= IntPtr.Zero;
- }
- }
- }
-
- private void DisposeBuffers()
- {
- if (buffers != null)
- {
- foreach (var buffer in buffers)
- {
- buffer.Dispose();
- }
- buffers = null;
- }
- }
-
- /// <summary>
- /// Finalizer. Only called when user forgets to call <see>Dispose</see>
- /// </summary>
- ~WaveOutEvent()
- {
- System.Diagnostics.Debug.Assert(false, "WaveOutEvent device was not closed");
- Dispose(false);
- }
-
- #endregion
-
- private void RaisePlaybackStoppedEvent(Exception e)
- {
- var handler = PlaybackStopped;
- if (handler != null)
- {
- if (syncContext == null)
- {
- handler(this, new StoppedEventArgs(e));
- }
- else
- {
- this.syncContext.Post(state => handler(this, new StoppedEventArgs(e)), null);
- }
- }
- }
- }
- }