Audio and MIDI library for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

283 lines
9.1 KiB

  1. using System;
  2. using System.Runtime.InteropServices;
  3. using NAudio.Mixer;
  4. using System.Threading;
  5. using NAudio.CoreAudioApi;
  6. // ReSharper disable once CheckNamespace
  7. namespace NAudio.Wave
  8. {
  9. /// <summary>
  10. /// Recording using waveIn api with event callbacks.
  11. /// Use this for recording in non-gui applications
  12. /// Events are raised as recorded buffers are made available
  13. /// </summary>
  14. public class WaveInEvent : IWaveIn
  15. {
  16. private readonly AutoResetEvent callbackEvent;
  17. private readonly SynchronizationContext syncContext;
  18. private IntPtr waveInHandle;
  19. private volatile CaptureState captureState;
  20. private WaveInBuffer[] buffers;
  21. /// <summary>
  22. /// Indicates recorded data is available
  23. /// </summary>
  24. public event EventHandler<WaveInEventArgs> DataAvailable;
  25. /// <summary>
  26. /// Indicates that all recorded data has now been received.
  27. /// </summary>
  28. public event EventHandler<StoppedEventArgs> RecordingStopped;
  29. /// <summary>
  30. /// Prepares a Wave input device for recording
  31. /// </summary>
  32. public WaveInEvent()
  33. {
  34. callbackEvent = new AutoResetEvent(false);
  35. syncContext = SynchronizationContext.Current;
  36. DeviceNumber = 0;
  37. WaveFormat = new WaveFormat(8000, 16, 1);
  38. BufferMilliseconds = 100;
  39. NumberOfBuffers = 3;
  40. captureState = CaptureState.Stopped;
  41. }
  42. /// <summary>
  43. /// Returns the number of Wave In devices available in the system
  44. /// </summary>
  45. public static int DeviceCount => WaveInterop.waveInGetNumDevs();
  46. /// <summary>
  47. /// Retrieves the capabilities of a waveIn device
  48. /// </summary>
  49. /// <param name="devNumber">Device to test</param>
  50. /// <returns>The WaveIn device capabilities</returns>
  51. public static WaveInCapabilities GetCapabilities(int devNumber)
  52. {
  53. WaveInCapabilities caps = new WaveInCapabilities();
  54. int structSize = Marshal.SizeOf(caps);
  55. MmException.Try(WaveInterop.waveInGetDevCaps((IntPtr)devNumber, out caps, structSize), "waveInGetDevCaps");
  56. return caps;
  57. }
  58. /// <summary>
  59. /// Milliseconds for the buffer. Recommended value is 100ms
  60. /// </summary>
  61. public int BufferMilliseconds { get; set; }
  62. /// <summary>
  63. /// Number of Buffers to use (usually 2 or 3)
  64. /// </summary>
  65. public int NumberOfBuffers { get; set; }
  66. /// <summary>
  67. /// The device number to use
  68. /// </summary>
  69. public int DeviceNumber { get; set; }
  70. private void CreateBuffers()
  71. {
  72. // Default to three buffers of 100ms each
  73. int bufferSize = BufferMilliseconds * WaveFormat.AverageBytesPerSecond / 1000;
  74. if (bufferSize % WaveFormat.BlockAlign != 0)
  75. {
  76. bufferSize -= bufferSize % WaveFormat.BlockAlign;
  77. }
  78. buffers = new WaveInBuffer[NumberOfBuffers];
  79. for (int n = 0; n < buffers.Length; n++)
  80. {
  81. buffers[n] = new WaveInBuffer(waveInHandle, bufferSize);
  82. }
  83. }
  84. private void OpenWaveInDevice()
  85. {
  86. CloseWaveInDevice();
  87. MmResult result = WaveInterop.waveInOpenWindow(out waveInHandle, (IntPtr)DeviceNumber, WaveFormat,
  88. callbackEvent.SafeWaitHandle.DangerousGetHandle(),
  89. IntPtr.Zero, WaveInterop.WaveInOutOpenFlags.CallbackEvent);
  90. MmException.Try(result, "waveInOpen");
  91. CreateBuffers();
  92. }
  93. /// <summary>
  94. /// Start recording
  95. /// </summary>
  96. public void StartRecording()
  97. {
  98. if (captureState != CaptureState.Stopped)
  99. throw new InvalidOperationException("Already recording");
  100. OpenWaveInDevice();
  101. MmException.Try(WaveInterop.waveInStart(waveInHandle), "waveInStart");
  102. captureState = CaptureState.Starting;
  103. ThreadPool.QueueUserWorkItem((state) => RecordThread(), null);
  104. }
  105. private void RecordThread()
  106. {
  107. Exception exception = null;
  108. try
  109. {
  110. DoRecording();
  111. }
  112. catch (Exception e)
  113. {
  114. exception = e;
  115. }
  116. finally
  117. {
  118. captureState = CaptureState.Stopped;
  119. RaiseRecordingStoppedEvent(exception);
  120. }
  121. }
  122. private void DoRecording()
  123. {
  124. captureState = CaptureState.Capturing;
  125. foreach (var buffer in buffers)
  126. {
  127. if (!buffer.InQueue)
  128. {
  129. buffer.Reuse();
  130. }
  131. }
  132. while (captureState == CaptureState.Capturing)
  133. {
  134. if (callbackEvent.WaitOne())
  135. {
  136. // requeue any buffers returned to us
  137. foreach (var buffer in buffers)
  138. {
  139. if (buffer.Done)
  140. {
  141. if (buffer.BytesRecorded > 0)
  142. {
  143. DataAvailable?.Invoke(this, new WaveInEventArgs(buffer.Data, buffer.BytesRecorded));
  144. }
  145. if (captureState == CaptureState.Capturing)
  146. {
  147. buffer.Reuse();
  148. }
  149. }
  150. }
  151. }
  152. }
  153. }
  154. private void RaiseRecordingStoppedEvent(Exception e)
  155. {
  156. var handler = RecordingStopped;
  157. if (handler != null)
  158. {
  159. if (syncContext == null)
  160. {
  161. handler(this, new StoppedEventArgs(e));
  162. }
  163. else
  164. {
  165. syncContext.Post(state => handler(this, new StoppedEventArgs(e)), null);
  166. }
  167. }
  168. }
  169. /// <summary>
  170. /// Stop recording
  171. /// </summary>
  172. public void StopRecording()
  173. {
  174. if (captureState != CaptureState.Stopped)
  175. {
  176. captureState = CaptureState.Stopping;
  177. MmException.Try(WaveInterop.waveInStop(waveInHandle), "waveInStop");
  178. //Reset, triggering the buffers to be returned
  179. MmException.Try(WaveInterop.waveInReset(waveInHandle), "waveInReset");
  180. callbackEvent.Set(); // signal the thread to exit
  181. }
  182. }
  183. /// <summary>
  184. /// Gets the current position in bytes from the wave input device.
  185. /// it calls directly into waveInGetPosition)
  186. /// </summary>
  187. /// <returns>Position in bytes</returns>
  188. public long GetPosition()
  189. {
  190. MmTime mmTime = new MmTime();
  191. mmTime.wType = MmTime.TIME_BYTES; // request results in bytes, TODO: perhaps make this a little more flexible and support the other types?
  192. MmException.Try(WaveInterop.waveInGetPosition(waveInHandle, out mmTime, Marshal.SizeOf(mmTime)), "waveInGetPosition");
  193. if (mmTime.wType != MmTime.TIME_BYTES)
  194. throw new Exception(string.Format("waveInGetPosition: wType -> Expected {0}, Received {1}", MmTime.TIME_BYTES, mmTime.wType));
  195. return mmTime.cb;
  196. }
  197. /// <summary>
  198. /// WaveFormat we are recording in
  199. /// </summary>
  200. public WaveFormat WaveFormat { get; set; }
  201. /// <summary>
  202. /// Dispose pattern
  203. /// </summary>
  204. protected virtual void Dispose(bool disposing)
  205. {
  206. if (disposing)
  207. {
  208. if (captureState != CaptureState.Stopped)
  209. StopRecording();
  210. CloseWaveInDevice();
  211. }
  212. }
  213. private void CloseWaveInDevice()
  214. {
  215. // Some drivers need the reset to properly release buffers
  216. WaveInterop.waveInReset(waveInHandle);
  217. if (buffers != null)
  218. {
  219. for (int n = 0; n < buffers.Length; n++)
  220. {
  221. buffers[n].Dispose();
  222. }
  223. buffers = null;
  224. }
  225. WaveInterop.waveInClose(waveInHandle);
  226. waveInHandle = IntPtr.Zero;
  227. }
  228. /// <summary>
  229. /// Microphone Level
  230. /// </summary>
  231. public MixerLine GetMixerLine()
  232. {
  233. // TODO use mixerGetID instead to see if this helps with XP
  234. MixerLine mixerLine;
  235. if (waveInHandle != IntPtr.Zero)
  236. {
  237. mixerLine = new MixerLine(waveInHandle, 0, MixerFlags.WaveInHandle);
  238. }
  239. else
  240. {
  241. mixerLine = new MixerLine((IntPtr)DeviceNumber, 0, MixerFlags.WaveIn);
  242. }
  243. return mixerLine;
  244. }
  245. /// <summary>
  246. /// Dispose method
  247. /// </summary>
  248. public void Dispose()
  249. {
  250. Dispose(true);
  251. GC.SuppressFinalize(this);
  252. }
  253. }
  254. }