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

using System;
using System.Runtime.InteropServices;
using NAudio.Mixer;
using System.Threading;
using NAudio.CoreAudioApi;
// ReSharper disable once CheckNamespace
namespace NAudio.Wave
{
/// <summary>
/// Recording using waveIn api with event callbacks.
/// Use this for recording in non-gui applications
/// Events are raised as recorded buffers are made available
/// </summary>
public class WaveInEvent : IWaveIn
{
private readonly AutoResetEvent callbackEvent;
private readonly SynchronizationContext syncContext;
private IntPtr waveInHandle;
private volatile CaptureState captureState;
private WaveInBuffer[] buffers;
/// <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>
/// Prepares a Wave input device for recording
/// </summary>
public WaveInEvent()
{
callbackEvent = new AutoResetEvent(false);
syncContext = SynchronizationContext.Current;
DeviceNumber = 0;
WaveFormat = new WaveFormat(8000, 16, 1);
BufferMilliseconds = 100;
NumberOfBuffers = 3;
captureState = CaptureState.Stopped;
}
/// <summary>
/// Returns the number of Wave In devices available in the system
/// </summary>
public static int DeviceCount => WaveInterop.waveInGetNumDevs();
/// <summary>
/// Retrieves the capabilities of a waveIn device
/// </summary>
/// <param name="devNumber">Device to test</param>
/// <returns>The WaveIn device capabilities</returns>
public static WaveInCapabilities GetCapabilities(int devNumber)
{
WaveInCapabilities caps = new WaveInCapabilities();
int structSize = Marshal.SizeOf(caps);
MmException.Try(WaveInterop.waveInGetDevCaps((IntPtr)devNumber, out caps, structSize), "waveInGetDevCaps");
return caps;
}
/// <summary>
/// Milliseconds for the buffer. Recommended value is 100ms
/// </summary>
public int BufferMilliseconds { get; set; }
/// <summary>
/// Number of Buffers to use (usually 2 or 3)
/// </summary>
public int NumberOfBuffers { get; set; }
/// <summary>
/// The device number to use
/// </summary>
public int DeviceNumber { get; set; }
private void CreateBuffers()
{
// Default to three buffers of 100ms each
int bufferSize = BufferMilliseconds * WaveFormat.AverageBytesPerSecond / 1000;
if (bufferSize % WaveFormat.BlockAlign != 0)
{
bufferSize -= bufferSize % WaveFormat.BlockAlign;
}
buffers = new WaveInBuffer[NumberOfBuffers];
for (int n = 0; n < buffers.Length; n++)
{
buffers[n] = new WaveInBuffer(waveInHandle, bufferSize);
}
}
private void OpenWaveInDevice()
{
CloseWaveInDevice();
MmResult result = WaveInterop.waveInOpenWindow(out waveInHandle, (IntPtr)DeviceNumber, WaveFormat,
callbackEvent.SafeWaitHandle.DangerousGetHandle(),
IntPtr.Zero, WaveInterop.WaveInOutOpenFlags.CallbackEvent);
MmException.Try(result, "waveInOpen");
CreateBuffers();
}
/// <summary>
/// Start recording
/// </summary>
public void StartRecording()
{
if (captureState != CaptureState.Stopped)
throw new InvalidOperationException("Already recording");
OpenWaveInDevice();
MmException.Try(WaveInterop.waveInStart(waveInHandle), "waveInStart");
captureState = CaptureState.Starting;
ThreadPool.QueueUserWorkItem((state) => RecordThread(), null);
}
private void RecordThread()
{
Exception exception = null;
try
{
DoRecording();
}
catch (Exception e)
{
exception = e;
}
finally
{
captureState = CaptureState.Stopped;
RaiseRecordingStoppedEvent(exception);
}
}
private void DoRecording()
{
captureState = CaptureState.Capturing;
foreach (var buffer in buffers)
{
if (!buffer.InQueue)
{
buffer.Reuse();
}
}
while (captureState == CaptureState.Capturing)
{
if (callbackEvent.WaitOne())
{
// requeue any buffers returned to us
foreach (var buffer in buffers)
{
if (buffer.Done)
{
if (buffer.BytesRecorded > 0)
{
DataAvailable?.Invoke(this, new WaveInEventArgs(buffer.Data, buffer.BytesRecorded));
}
if (captureState == CaptureState.Capturing)
{
buffer.Reuse();
}
}
}
}
}
}
private void RaiseRecordingStoppedEvent(Exception e)
{
var handler = RecordingStopped;
if (handler != null)
{
if (syncContext == null)
{
handler(this, new StoppedEventArgs(e));
}
else
{
syncContext.Post(state => handler(this, new StoppedEventArgs(e)), null);
}
}
}
/// <summary>
/// Stop recording
/// </summary>
public void StopRecording()
{
if (captureState != CaptureState.Stopped)
{
captureState = CaptureState.Stopping;
MmException.Try(WaveInterop.waveInStop(waveInHandle), "waveInStop");
//Reset, triggering the buffers to be returned
MmException.Try(WaveInterop.waveInReset(waveInHandle), "waveInReset");
callbackEvent.Set(); // signal the thread to exit
}
}
/// <summary>
/// Gets the current position in bytes from the wave input device.
/// it calls directly into waveInGetPosition)
/// </summary>
/// <returns>Position in bytes</returns>
public long GetPosition()
{
MmTime 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.waveInGetPosition(waveInHandle, out mmTime, Marshal.SizeOf(mmTime)), "waveInGetPosition");
if (mmTime.wType != MmTime.TIME_BYTES)
throw new Exception(string.Format("waveInGetPosition: wType -> Expected {0}, Received {1}", MmTime.TIME_BYTES, mmTime.wType));
return mmTime.cb;
}
/// <summary>
/// WaveFormat we are recording in
/// </summary>
public WaveFormat WaveFormat { get; set; }
/// <summary>
/// Dispose pattern
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (captureState != CaptureState.Stopped)
StopRecording();
CloseWaveInDevice();
}
}
private void CloseWaveInDevice()
{
// Some drivers need the reset to properly release buffers
WaveInterop.waveInReset(waveInHandle);
if (buffers != null)
{
for (int n = 0; n < buffers.Length; n++)
{
buffers[n].Dispose();
}
buffers = null;
}
WaveInterop.waveInClose(waveInHandle);
waveInHandle = IntPtr.Zero;
}
/// <summary>
/// Microphone Level
/// </summary>
public MixerLine GetMixerLine()
{
// TODO use mixerGetID instead to see if this helps with XP
MixerLine mixerLine;
if (waveInHandle != IntPtr.Zero)
{
mixerLine = new MixerLine(waveInHandle, 0, MixerFlags.WaveInHandle);
}
else
{
mixerLine = new MixerLine((IntPtr)DeviceNumber, 0, MixerFlags.WaveIn);
}
return mixerLine;
}
/// <summary>
/// Dispose method
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}