using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Runtime.InteropServices; using LumiSoft.Media.Wave.Native; namespace LumiSoft.Media.Wave { #region Delegates Implementation /// /// Represents the method that will handle the WavRecorder.BufferFull event. /// /// Recorded data. public delegate void BufferFullHandler(byte[] buffer); #endregion /// /// This class implements streaming microphone wav data receiver. /// public class WaveIn { #region class BufferItem /// /// This class holds queued recording buffer. /// private class BufferItem { private GCHandle m_HeaderHandle; private GCHandle m_DataHandle; private int m_DataSize = 0; /// /// Default constructor. /// /// Header handle. /// Wav header. /// Wav header data handle. /// Data size in bytes. public BufferItem(ref GCHandle headerHandle,ref GCHandle dataHandle,int dataSize) { m_HeaderHandle = headerHandle; m_DataHandle = dataHandle; m_DataSize = dataSize; } #region method Dispose /// /// Cleans up any resources being used. /// public void Dispose() { m_HeaderHandle.Free(); m_DataHandle.Free(); } #endregion #region Properties Implementation /// /// Gets header handle. /// public GCHandle HeaderHandle { get{ return m_HeaderHandle; } } /// /// Gets header. /// public WAVEHDR Header { get{ return (WAVEHDR)m_HeaderHandle.Target; } } /// /// Gets wav header data pointer handle. /// public GCHandle DataHandle { get{ return m_DataHandle; } } /// /// Gets wav header data. /// public byte[] Data { get{ return (byte[])m_DataHandle.Target; } } /// /// Gets wav header data size in bytes. /// public int DataSize { get{ return m_DataSize; } } #endregion } #endregion private WavInDevice m_pInDevice = null; private int m_SamplesPerSec = 8000; private int m_BitsPerSample = 8; private int m_Channels = 1; private int m_BufferSize = 400; private IntPtr m_pWavDevHandle = IntPtr.Zero; private int m_BlockSize = 0; private List m_pBuffers = null; private waveInProc m_pWaveInProc = null; private bool m_IsRecording = false; private bool m_IsDisposed = false; /// /// Default constructor. /// /// Input device. /// Sample rate, in samples per second (hertz). For PCM common values are /// 8.0 kHz, 11.025 kHz, 22.05 kHz, and 44.1 kHz. /// Bits per sample. For PCM 8 or 16 are the only valid values. /// Number of channels. /// Specifies recording buffer size. /// Is raised when outputDevice is null. /// Is raised when any of the aruments has invalid value. public WaveIn(WavInDevice device,int samplesPerSec,int bitsPerSample,int channels,int bufferSize) { if(device == null){ throw new ArgumentNullException("device"); } if(samplesPerSec < 8000){ throw new ArgumentException("Argument 'samplesPerSec' value must be >= 8000."); } if(bitsPerSample < 8){ throw new ArgumentException("Argument 'bitsPerSample' value must be >= 8."); } if(channels < 1){ throw new ArgumentException("Argument 'channels' value must be >= 1."); } m_pInDevice = device; m_SamplesPerSec = samplesPerSec; m_BitsPerSample = bitsPerSample; m_Channels = channels; m_BufferSize = bufferSize; m_BlockSize = m_Channels * (m_BitsPerSample / 8); m_pBuffers = new List(); // Try to open wav device. WAVEFORMATEX format = new WAVEFORMATEX(); format.wFormatTag = WavFormat.PCM; format.nChannels = (ushort)m_Channels; format.nSamplesPerSec = (uint)samplesPerSec; format.nAvgBytesPerSec = (uint)(m_SamplesPerSec * m_Channels * (m_BitsPerSample / 8)); format.nBlockAlign = (ushort)m_BlockSize; format.wBitsPerSample = (ushort)m_BitsPerSample; format.cbSize = 0; // We must delegate reference, otherwise GC will collect it. m_pWaveInProc = new waveInProc(this.OnWaveInProc); int result = WavMethods.waveInOpen(out m_pWavDevHandle,m_pInDevice.Index,format,m_pWaveInProc,0,WavConstants.CALLBACK_FUNCTION); if(result != MMSYSERR.NOERROR){ throw new Exception("Failed to open wav device, error: " + result.ToString() + "."); } EnsureBuffers(); } /// /// Default destructor. /// ~WaveIn() { Dispose(); } #region method Dispose /// /// Cleans up any resources being used. /// public void Dispose() { if(m_IsDisposed){ return; } m_IsDisposed = true; // Release events. this.BufferFull = null; try{ // If recording, we need to reset wav device first. WavMethods.waveInReset(m_pWavDevHandle); // If there are unprepared wav headers, we need to unprepare these. foreach(BufferItem item in m_pBuffers){ WavMethods.waveInUnprepareHeader(m_pWavDevHandle,item.HeaderHandle.AddrOfPinnedObject(),Marshal.SizeOf(item.Header)); item.Dispose(); } // Close input device. WavMethods.waveInClose(m_pWavDevHandle); m_pInDevice = null; m_pWavDevHandle = IntPtr.Zero; } catch{ } } #endregion #region method Start /// /// Starts recording. /// public void Start() { if(m_IsRecording){ return; } m_IsRecording = true; int result = WavMethods.waveInStart(m_pWavDevHandle); if(result != MMSYSERR.NOERROR){ throw new Exception("Failed to start wav device, error: " + result + "."); } } #endregion #region method Stop /// /// Stops recording. /// public void Stop() { if(!m_IsRecording){ return; } m_IsRecording = false; int result = WavMethods.waveInStop(m_pWavDevHandle); if(result != MMSYSERR.NOERROR){ throw new Exception("Failed to stop wav device, error: " + result + "."); } } #endregion #region method OnWaveInProc /// /// This method is called when wav device generates some event. /// /// Handle to the waveform-audio device associated with the callback. /// Waveform-audio input message. /// User-instance data specified with waveOutOpen. /// Message parameter. /// Message parameter. private void OnWaveInProc(IntPtr hdrvr,int uMsg,int dwUser,int dwParam1,int dwParam2) { // NOTE: MSDN warns, we may not call any wav related methods here. try{ if(uMsg == WavConstants.MM_WIM_DATA){ ThreadPool.QueueUserWorkItem(new WaitCallback(this.ProcessFirstBuffer)); } } catch{ } } #endregion #region method ProcessFirstBuffer /// /// Processes first first filled buffer in queue and disposes it if done. /// /// User data. private void ProcessFirstBuffer(object state) { try{ lock(m_pBuffers){ BufferItem item = m_pBuffers[0]; // Raise BufferFull event. OnBufferFull(item.Data); // Clean up. WavMethods.waveInUnprepareHeader(m_pWavDevHandle,item.HeaderHandle.AddrOfPinnedObject(),Marshal.SizeOf(item.Header)); m_pBuffers.Remove(item); item.Dispose(); } EnsureBuffers(); } catch{ } } #endregion #region method EnsureBuffers /// /// Fills recording buffers. /// private void EnsureBuffers() { // We keep 3 x buffer. lock(m_pBuffers){ while(m_pBuffers.Count < 3){ byte[] data = new byte[m_BufferSize]; GCHandle dataHandle = GCHandle.Alloc(data,GCHandleType.Pinned); WAVEHDR wavHeader = new WAVEHDR(); wavHeader.lpData = dataHandle.AddrOfPinnedObject(); wavHeader.dwBufferLength = (uint)data.Length; wavHeader.dwBytesRecorded = 0; wavHeader.dwUser = IntPtr.Zero; wavHeader.dwFlags = 0; wavHeader.dwLoops = 0; wavHeader.lpNext = IntPtr.Zero; wavHeader.reserved = 0; GCHandle headerHandle = GCHandle.Alloc(wavHeader,GCHandleType.Pinned); int result = 0; result = WavMethods.waveInPrepareHeader(m_pWavDevHandle,headerHandle.AddrOfPinnedObject(),Marshal.SizeOf(wavHeader)); if(result == MMSYSERR.NOERROR){ m_pBuffers.Add(new BufferItem(ref headerHandle,ref dataHandle,m_BufferSize)); result = WavMethods.waveInAddBuffer(m_pWavDevHandle,headerHandle.AddrOfPinnedObject(),Marshal.SizeOf(wavHeader)); if(result != MMSYSERR.NOERROR){ throw new Exception("Error adding wave in buffer, error: " + result + "."); } } } } } #endregion #region Properties Implementation /// /// Gets all available input audio devices. /// public static WavInDevice[] Devices { get{ List retVal = new List(); // Get all available output devices and their info. int devicesCount = WavMethods.waveInGetNumDevs(); for(int i=0;i /// Gets if this object is disposed. /// public bool IsDisposed { get{ return m_IsDisposed; } } /// /// Gets current input device. /// /// Is raised when this object is disposed and this property is accessed. public WavInDevice InputDevice { get{ if(m_IsDisposed){ throw new ObjectDisposedException("WavRecorder"); } return m_pInDevice; } } /// /// Gets number of samples per second. /// /// Is raised when this object is disposed and this property is accessed. public int SamplesPerSec { get{ if(m_IsDisposed){ throw new ObjectDisposedException("WavRecorder"); } return m_SamplesPerSec; } } /// /// Gets number of buts per sample. /// /// Is raised when this object is disposed and this property is accessed. public int BitsPerSample { get{ if(m_IsDisposed){ throw new ObjectDisposedException("WavRecorder"); } return m_BitsPerSample; } } /// /// Gets number of channels. /// /// Is raised when this object is disposed and this property is accessed. public int Channels { get{ if(m_IsDisposed){ throw new ObjectDisposedException("WavRecorder"); } return m_Channels; } } /// /// Gets recording buffer size. /// /// Is raised when this object is disposed and this property is accessed. public int BufferSize { get{ if(m_IsDisposed){ throw new ObjectDisposedException("WavRecorder"); } return m_BufferSize; } } // /// Gets one smaple block size in bytes. /// /// Is raised when this object is disposed and this property is accessed. public int BlockSize { get{ if(m_IsDisposed){ throw new ObjectDisposedException("WavRecorder"); } return m_BlockSize; } } #endregion #region Events Implementation /// /// This event is raised when record buffer is full and application should process it. /// public event BufferFullHandler BufferFull = null; /// /// This method raises event BufferFull event. /// /// Receive buffer. private void OnBufferFull(byte[] buffer) { if(this.BufferFull != null){ this.BufferFull(buffer); } } #endregion } }