/NAudio/Wave/WaveOutputs/WaveOutEvent.cs

# · C# · 374 lines · 265 code · 36 blank · 73 comment · 52 complexity · 0edd754399289556590243aac6b2b6b0 MD5 · raw file

  1. using System;
  2. using System.Diagnostics;
  3. using System.Threading;
  4. using System.Runtime.InteropServices;
  5. using System.Windows.Forms.VisualStyles;
  6. namespace NAudio.Wave
  7. {
  8. /// <summary>
  9. /// Alternative WaveOut class, making use of the Event callback
  10. /// </summary>
  11. public class WaveOutEvent : IWavePlayer, IWavePosition
  12. {
  13. private readonly object waveOutLock;
  14. private readonly SynchronizationContext syncContext;
  15. private IntPtr hWaveOut; // WaveOut handle
  16. private WaveOutBuffer[] buffers;
  17. private IWaveProvider waveStream;
  18. private volatile PlaybackState playbackState;
  19. private AutoResetEvent callbackEvent;
  20. private float volume = 1.0f;
  21. /// <summary>
  22. /// Indicates playback has stopped automatically
  23. /// </summary>
  24. public event EventHandler<StoppedEventArgs> PlaybackStopped;
  25. /// <summary>
  26. /// Gets or sets the desired latency in milliseconds
  27. /// Should be set before a call to Init
  28. /// </summary>
  29. public int DesiredLatency { get; set; }
  30. /// <summary>
  31. /// Gets or sets the number of buffers used
  32. /// Should be set before a call to Init
  33. /// </summary>
  34. public int NumberOfBuffers { get; set; }
  35. /// <summary>
  36. /// Gets or sets the device number
  37. /// Should be set before a call to Init
  38. /// This must be between 0 and <see>DeviceCount</see> - 1.
  39. /// </summary>
  40. public int DeviceNumber { get; set; }
  41. /// <summary>
  42. /// Opens a WaveOut device
  43. /// </summary>
  44. public WaveOutEvent()
  45. {
  46. syncContext = SynchronizationContext.Current;
  47. if (syncContext != null &&
  48. ((syncContext.GetType().Name == "LegacyAspNetSynchronizationContext") ||
  49. (syncContext.GetType().Name == "AspNetSynchronizationContext")))
  50. {
  51. syncContext = null;
  52. }
  53. // set default values up
  54. DeviceNumber = 0;
  55. DesiredLatency = 300;
  56. NumberOfBuffers = 2;
  57. this.waveOutLock = new object();
  58. }
  59. /// <summary>
  60. /// Initialises the WaveOut device
  61. /// </summary>
  62. /// <param name="waveProvider">WaveProvider to play</param>
  63. public void Init(IWaveProvider waveProvider)
  64. {
  65. if (playbackState != PlaybackState.Stopped)
  66. {
  67. throw new InvalidOperationException("Can't re-initialize during playback");
  68. }
  69. if (hWaveOut != IntPtr.Zero)
  70. {
  71. // normally we don't allow calling Init twice, but as experiment, see if we can clean up and go again
  72. // try to allow reuse of this waveOut device
  73. // n.b. risky if Playback thread has not exited
  74. DisposeBuffers();
  75. CloseWaveOut();
  76. }
  77. this.callbackEvent = new AutoResetEvent(false);
  78. this.waveStream = waveProvider;
  79. int bufferSize = waveProvider.WaveFormat.ConvertLatencyToByteSize((DesiredLatency + NumberOfBuffers - 1) / NumberOfBuffers);
  80. MmResult result;
  81. lock (waveOutLock)
  82. {
  83. result = WaveInterop.waveOutOpenWindow(out hWaveOut, (IntPtr)DeviceNumber, waveStream.WaveFormat, callbackEvent.SafeWaitHandle.DangerousGetHandle(), IntPtr.Zero, WaveInterop.WaveInOutOpenFlags.CallbackEvent);
  84. }
  85. MmException.Try(result, "waveOutOpen");
  86. buffers = new WaveOutBuffer[NumberOfBuffers];
  87. playbackState = PlaybackState.Stopped;
  88. for (int n = 0; n < NumberOfBuffers; n++)
  89. {
  90. buffers[n] = new WaveOutBuffer(hWaveOut, bufferSize, waveStream, waveOutLock);
  91. }
  92. }
  93. /// <summary>
  94. /// Start playing the audio from the WaveStream
  95. /// </summary>
  96. public void Play()
  97. {
  98. if (this.buffers == null || this.waveStream == null)
  99. {
  100. throw new InvalidOperationException("Must call Init first");
  101. }
  102. if (playbackState == PlaybackState.Stopped)
  103. {
  104. playbackState = PlaybackState.Playing;
  105. callbackEvent.Set(); // give the thread a kick
  106. ThreadPool.QueueUserWorkItem((state) => PlaybackThread(), null);
  107. }
  108. else if (playbackState == PlaybackState.Paused)
  109. {
  110. Resume();
  111. callbackEvent.Set(); // give the thread a kick
  112. }
  113. }
  114. private void PlaybackThread()
  115. {
  116. Exception exception = null;
  117. try
  118. {
  119. DoPlayback();
  120. }
  121. catch (Exception e)
  122. {
  123. exception = e;
  124. }
  125. finally
  126. {
  127. playbackState = PlaybackState.Stopped;
  128. // we're exiting our background thread
  129. RaisePlaybackStoppedEvent(exception);
  130. }
  131. }
  132. private void DoPlayback()
  133. {
  134. while (playbackState != PlaybackState.Stopped)
  135. {
  136. if (!callbackEvent.WaitOne(DesiredLatency))
  137. Debug.WriteLine("WARNING: WaveOutEvent callback event timeout");
  138. // requeue any buffers returned to us
  139. if (playbackState == PlaybackState.Playing)
  140. {
  141. int queued = 0;
  142. foreach (var buffer in buffers)
  143. {
  144. if (buffer.InQueue || buffer.OnDone())
  145. {
  146. queued++;
  147. }
  148. }
  149. if (queued == 0)
  150. {
  151. // we got to the end
  152. this.playbackState = PlaybackState.Stopped;
  153. callbackEvent.Set();
  154. }
  155. }
  156. }
  157. }
  158. /// <summary>
  159. /// Pause the audio
  160. /// </summary>
  161. public void Pause()
  162. {
  163. if (playbackState == PlaybackState.Playing)
  164. {
  165. MmResult result;
  166. lock (waveOutLock)
  167. {
  168. result = WaveInterop.waveOutPause(hWaveOut);
  169. }
  170. if (result != MmResult.NoError)
  171. {
  172. throw new MmException(result, "waveOutPause");
  173. }
  174. playbackState = PlaybackState.Paused;
  175. }
  176. }
  177. /// <summary>
  178. /// Resume playing after a pause from the same position
  179. /// </summary>
  180. private void Resume()
  181. {
  182. if (playbackState == PlaybackState.Paused)
  183. {
  184. MmResult result;
  185. lock (waveOutLock)
  186. {
  187. result = WaveInterop.waveOutRestart(hWaveOut);
  188. }
  189. if (result != MmResult.NoError)
  190. {
  191. throw new MmException(result, "waveOutRestart");
  192. }
  193. playbackState = PlaybackState.Playing;
  194. }
  195. }
  196. /// <summary>
  197. /// Stop and reset the WaveOut device
  198. /// </summary>
  199. public void Stop()
  200. {
  201. if (playbackState != PlaybackState.Stopped)
  202. {
  203. // in the call to waveOutReset with function callbacks
  204. // some drivers will block here until OnDone is called
  205. // for every buffer
  206. playbackState = PlaybackState.Stopped; // set this here to avoid a problem with some drivers whereby
  207. MmResult result;
  208. lock (waveOutLock)
  209. {
  210. result = WaveInterop.waveOutReset(hWaveOut);
  211. }
  212. if (result != MmResult.NoError)
  213. {
  214. throw new MmException(result, "waveOutReset");
  215. }
  216. callbackEvent.Set(); // give the thread a kick, make sure we exit
  217. }
  218. }
  219. /// <summary>
  220. /// Gets the current position in bytes from the wave output device.
  221. /// (n.b. this is not the same thing as the position within your reader
  222. /// stream - it calls directly into waveOutGetPosition)
  223. /// </summary>
  224. /// <returns>Position in bytes</returns>
  225. public long GetPosition()
  226. {
  227. lock (waveOutLock)
  228. {
  229. var mmTime = new MmTime();
  230. mmTime.wType = MmTime.TIME_BYTES; // request results in bytes, TODO: perhaps make this a little more flexible and support the other types?
  231. MmException.Try(WaveInterop.waveOutGetPosition(hWaveOut, out mmTime, Marshal.SizeOf(mmTime)), "waveOutGetPosition");
  232. if (mmTime.wType != MmTime.TIME_BYTES)
  233. throw new Exception(string.Format("waveOutGetPosition: wType -> Expected {0}, Received {1}", MmTime.TIME_BYTES, mmTime.wType));
  234. return mmTime.cb;
  235. }
  236. }
  237. /// <summary>
  238. /// Gets a <see cref="Wave.WaveFormat"/> instance indicating the format the hardware is using.
  239. /// </summary>
  240. public WaveFormat OutputWaveFormat
  241. {
  242. get { return this.waveStream.WaveFormat; }
  243. }
  244. /// <summary>
  245. /// Playback State
  246. /// </summary>
  247. public PlaybackState PlaybackState
  248. {
  249. get { return playbackState; }
  250. }
  251. /// <summary>
  252. /// Obsolete property
  253. /// </summary>
  254. [Obsolete]
  255. public float Volume
  256. {
  257. get { return volume; }
  258. set
  259. {
  260. WaveOut.SetWaveOutVolume(value, hWaveOut, waveOutLock);
  261. volume = value;
  262. }
  263. }
  264. #region Dispose Pattern
  265. /// <summary>
  266. /// Closes this WaveOut device
  267. /// </summary>
  268. public void Dispose()
  269. {
  270. GC.SuppressFinalize(this);
  271. Dispose(true);
  272. }
  273. /// <summary>
  274. /// Closes the WaveOut device and disposes of buffers
  275. /// </summary>
  276. /// <param name="disposing">True if called from <see>Dispose</see></param>
  277. protected void Dispose(bool disposing)
  278. {
  279. Stop();
  280. if (disposing)
  281. {
  282. DisposeBuffers();
  283. }
  284. CloseWaveOut();
  285. }
  286. private void CloseWaveOut()
  287. {
  288. if (callbackEvent != null)
  289. {
  290. callbackEvent.Close();
  291. callbackEvent = null;
  292. }
  293. lock (waveOutLock)
  294. {
  295. if (hWaveOut != IntPtr.Zero)
  296. {
  297. WaveInterop.waveOutClose(hWaveOut);
  298. hWaveOut= IntPtr.Zero;
  299. }
  300. }
  301. }
  302. private void DisposeBuffers()
  303. {
  304. if (buffers != null)
  305. {
  306. foreach (var buffer in buffers)
  307. {
  308. buffer.Dispose();
  309. }
  310. buffers = null;
  311. }
  312. }
  313. /// <summary>
  314. /// Finalizer. Only called when user forgets to call <see>Dispose</see>
  315. /// </summary>
  316. ~WaveOutEvent()
  317. {
  318. System.Diagnostics.Debug.Assert(false, "WaveOutEvent device was not closed");
  319. Dispose(false);
  320. }
  321. #endregion
  322. private void RaisePlaybackStoppedEvent(Exception e)
  323. {
  324. var handler = PlaybackStopped;
  325. if (handler != null)
  326. {
  327. if (syncContext == null)
  328. {
  329. handler(this, new StoppedEventArgs(e));
  330. }
  331. else
  332. {
  333. this.syncContext.Post(state => handler(this, new StoppedEventArgs(e)), null);
  334. }
  335. }
  336. }
  337. }
  338. }