mirror of https://github.com/naudio/NAudio.git
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
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);
|
|
}
|
|
}
|
|
}
|
|
|