/NAudio/Wave/WaveInputs/WasapiCapture.cs
# · C# · 342 lines · 243 code · 38 blank · 61 comment · 32 complexity · 482cbc640710d5ffbd91627dd7efffd5 MD5 · raw file
- using System;
- using System.Collections.Generic;
- using System.Text;
- using NAudio.Wave;
- using System.Threading;
- using System.Diagnostics;
- using System.Runtime.InteropServices;
-
- // for consistency this should be in NAudio.Wave namespace, but left as it is for backwards compatibility
- namespace NAudio.CoreAudioApi
- {
- /// <summary>
- /// Audio Capture using Wasapi
- /// See http://msdn.microsoft.com/en-us/library/dd370800%28VS.85%29.aspx
- /// </summary>
- public class WasapiCapture : IWaveIn
- {
- private const long REFTIMES_PER_SEC = 10000000;
- private const long REFTIMES_PER_MILLISEC = 10000;
- private volatile bool requestStop;
- private byte[] recordBuffer;
- private Thread captureThread;
- private AudioClient audioClient;
- private int bytesPerFrame;
- private WaveFormat waveFormat;
- private bool initialized;
- private readonly SynchronizationContext syncContext;
- private readonly bool isUsingEventSync;
- private EventWaitHandle frameEventWaitHandle;
-
- /// <summary>
- /// Indicates recorded data is available
- /// </summary>
- public event EventHandler<WaveInEventArgs> DataAvailable;
-
- /// <summary>
- /// Indicates that all recorded data has now been received.
- /// </summary>
- public event EventHandler<StoppedEventArgs> RecordingStopped;
-
- /// <summary>
- /// Initialises a new instance of the WASAPI capture class
- /// </summary>
- public WasapiCapture() :
- this(GetDefaultCaptureDevice())
- {
- }
-
- /// <summary>
- /// Initialises a new instance of the WASAPI capture class
- /// </summary>
- /// <param name="captureDevice">Capture device to use</param>
- public WasapiCapture(MMDevice captureDevice)
- : this(captureDevice, false)
- {
-
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="WasapiCapture"/> class.
- /// </summary>
- /// <param name="captureDevice">The capture device.</param>
- /// <param name="useEventSync">true if sync is done with event. false use sleep.</param>
- public WasapiCapture(MMDevice captureDevice, bool useEventSync)
- {
- syncContext = SynchronizationContext.Current;
- audioClient = captureDevice.AudioClient;
- ShareMode = AudioClientShareMode.Shared;
- isUsingEventSync = useEventSync;
-
- waveFormat = audioClient.MixFormat;
-
- }
-
- /// <summary>
- /// Share Mode - set before calling StartRecording
- /// </summary>
- public AudioClientShareMode ShareMode { get; set; }
-
- /// <summary>
- /// Recording wave format
- /// </summary>
- public virtual WaveFormat WaveFormat
- {
- get
- {
- // for convenience, return a WAVEFORMATEX, instead of the real
- // WAVEFORMATEXTENSIBLE being used
- var wfe = waveFormat as WaveFormatExtensible;
- if (wfe != null)
- {
- try
- {
- return wfe.ToStandardWaveFormat();
- }
- catch (InvalidOperationException)
- {
- // couldn't convert to a standard format
- }
- }
- return waveFormat;
- }
- set { waveFormat = value; }
- }
-
- /// <summary>
- /// Gets the default audio capture device
- /// </summary>
- /// <returns>The default audio capture device</returns>
- public static MMDevice GetDefaultCaptureDevice()
- {
- var devices = new MMDeviceEnumerator();
- return devices.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Console);
- }
-
- private void InitializeCaptureDevice()
- {
- if (initialized)
- return;
-
- long requestedDuration = REFTIMES_PER_MILLISEC * 100;
-
- if (!audioClient.IsFormatSupported(ShareMode, waveFormat))
- {
- throw new ArgumentException("Unsupported Wave Format");
- }
-
- var streamFlags = GetAudioClientStreamFlags();
-
- // If using EventSync, setup is specific with shareMode
- if (isUsingEventSync)
- {
- // Init Shared or Exclusive
- if (ShareMode == AudioClientShareMode.Shared)
- {
- // With EventCallBack and Shared, both latencies must be set to 0
- audioClient.Initialize(ShareMode, AudioClientStreamFlags.EventCallback, requestedDuration, 0,
- this.waveFormat, Guid.Empty);
- }
- else
- {
- // With EventCallBack and Exclusive, both latencies must equals
- audioClient.Initialize(ShareMode, AudioClientStreamFlags.EventCallback, requestedDuration, requestedDuration,
- this.waveFormat, Guid.Empty);
- }
-
- // Create the Wait Event Handle
- frameEventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
- audioClient.SetEventHandle(frameEventWaitHandle.SafeWaitHandle.DangerousGetHandle());
- }
- else
- {
- // Normal setup for both sharedMode
- audioClient.Initialize(ShareMode,
- streamFlags,
- requestedDuration,
- 0,
- this.waveFormat,
- Guid.Empty);
- }
-
- int bufferFrameCount = audioClient.BufferSize;
- this.bytesPerFrame = this.waveFormat.Channels * this.waveFormat.BitsPerSample / 8;
- this.recordBuffer = new byte[bufferFrameCount * bytesPerFrame];
- Debug.WriteLine(string.Format("record buffer size = {0}", this.recordBuffer.Length));
-
- initialized = true;
- }
-
- /// <summary>
- /// To allow overrides to specify different flags (e.g. loopback)
- /// </summary>
- protected virtual AudioClientStreamFlags GetAudioClientStreamFlags()
- {
- return AudioClientStreamFlags.None;
- }
-
- /// <summary>
- /// Start Recording
- /// </summary>
- public void StartRecording()
- {
- if (captureThread != null)
- {
- throw new InvalidOperationException("Previous recording still in progress");
- }
- InitializeCaptureDevice();
- ThreadStart start = () => CaptureThread(this.audioClient);
- this.captureThread = new Thread(start);
-
- Debug.WriteLine("Thread starting...");
- this.requestStop = false;
- this.captureThread.Start();
- }
-
- /// <summary>
- /// Stop Recording (requests a stop, wait for RecordingStopped event to know it has finished)
- /// </summary>
- public void StopRecording()
- {
- this.requestStop = true;
- }
-
- private void CaptureThread(AudioClient client)
- {
- Exception exception = null;
- try
- {
- DoRecording(client);
- }
- catch (Exception e)
- {
- exception = e;
- }
- finally
- {
- client.Stop();
- // don't dispose - the AudioClient only gets disposed when WasapiCapture is disposed
- }
- captureThread = null;
- RaiseRecordingStopped(exception);
- Debug.WriteLine("Stop wasapi");
- }
-
- private void DoRecording(AudioClient client)
- {
- Debug.WriteLine(String.Format("Client buffer frame count: {0}", client.BufferSize));
- int bufferFrameCount = client.BufferSize;
-
- // Calculate the actual duration of the allocated buffer.
- long actualDuration = (long)((double)REFTIMES_PER_SEC *
- bufferFrameCount / waveFormat.SampleRate);
- int sleepMilliseconds = (int)(actualDuration / REFTIMES_PER_MILLISEC / 2);
- int waitMilliseconds = (int)(3 * actualDuration / REFTIMES_PER_MILLISEC);
-
- AudioCaptureClient capture = client.AudioCaptureClient;
- client.Start();
-
- if (isUsingEventSync)
- {
- Debug.WriteLine(string.Format("wait: {0} ms", waitMilliseconds));
- }
- else
- {
- Debug.WriteLine(string.Format("sleep: {0} ms", sleepMilliseconds));
- }
-
- while (!this.requestStop)
- {
- bool readBuffer = true;
- if (isUsingEventSync)
- {
- readBuffer = frameEventWaitHandle.WaitOne(waitMilliseconds, false);
- }
- else
- {
- Thread.Sleep(sleepMilliseconds);
- }
-
- // If still playing and notification is ok
- if (!this.requestStop && readBuffer)
- {
- ReadNextPacket(capture);
- }
- }
- }
-
- private void RaiseRecordingStopped(Exception e)
- {
- var handler = RecordingStopped;
- if (handler == null) return;
- if (this.syncContext == null)
- {
- handler(this, new StoppedEventArgs(e));
- }
- else
- {
- this.syncContext.Post(state => handler(this, new StoppedEventArgs(e)), null);
- }
- }
-
- private void ReadNextPacket(AudioCaptureClient capture)
- {
- int packetSize = capture.GetNextPacketSize();
- int recordBufferOffset = 0;
- //Debug.WriteLine(string.Format("packet size: {0} samples", packetSize / 4));
-
- while (packetSize != 0)
- {
- int framesAvailable;
- AudioClientBufferFlags flags;
- IntPtr buffer = capture.GetBuffer(out framesAvailable, out flags);
-
- int bytesAvailable = framesAvailable * bytesPerFrame;
-
- // apparently it is sometimes possible to read more frames than we were expecting?
- // fix suggested by Michael Feld:
- int spaceRemaining = Math.Max(0, recordBuffer.Length - recordBufferOffset);
- if (spaceRemaining < bytesAvailable && recordBufferOffset > 0)
- {
- if (DataAvailable != null) DataAvailable(this, new WaveInEventArgs(recordBuffer, recordBufferOffset));
- recordBufferOffset = 0;
- }
-
- // if not silence...
- if ((flags & AudioClientBufferFlags.Silent) != AudioClientBufferFlags.Silent)
- {
- Marshal.Copy(buffer, recordBuffer, recordBufferOffset, bytesAvailable);
- }
- else
- {
- Array.Clear(recordBuffer, recordBufferOffset, bytesAvailable);
- }
- recordBufferOffset += bytesAvailable;
- capture.ReleaseBuffer(framesAvailable);
- packetSize = capture.GetNextPacketSize();
- }
- if (DataAvailable != null)
- {
- DataAvailable(this, new WaveInEventArgs(recordBuffer, recordBufferOffset));
- }
- }
-
- /// <summary>
- /// Dispose
- /// </summary>
- public void Dispose()
- {
- StopRecording();
- if (captureThread != null)
- {
- captureThread.Join();
- captureThread = null;
- }
- if (audioClient != null)
- {
- audioClient.Dispose();
- audioClient = null;
- }
- }
- }
- }