using System; using System.Runtime.InteropServices; using System.Threading; namespace Luski.net.Sound { internal unsafe class Recorder { internal Recorder() { delegateWaveInProc = new Win32.DelegateWaveInProc(WaveInProc); } private readonly LockerClass Locker = new(); private readonly LockerClass LockerCopy = new(); private IntPtr hWaveIn = IntPtr.Zero; private string WaveInDeviceName = ""; private bool IsWaveInOpened = false; private bool IsWaveInStarted = false; private bool IsThreadRecordingRunning = false; private bool IsDataIncomming = false; private bool Stopped = false; private int SamplesPerSecond = 8000; private int BitsPerSample = 16; private int Channels = 1; private int BufferCount = 8; private int BufferSize = 1024; private Win32.WAVEHDR*[] WaveInHeaders; private Win32.WAVEHDR* CurrentRecordedHeader; private readonly Win32.DelegateWaveInProc delegateWaveInProc; private Thread ThreadRecording; private readonly AutoResetEvent AutoResetEventDataRecorded = new(false); internal delegate void DelegateStopped(); internal delegate void DelegateDataRecorded(byte[] bytes); internal event DelegateStopped RecordingStopped; internal event DelegateDataRecorded DataRecorded; internal bool Started => IsWaveInStarted && IsWaveInOpened && IsThreadRecordingRunning; private bool CreateWaveInHeaders() { WaveInHeaders = new Win32.WAVEHDR*[BufferCount]; int createdHeaders = 0; for (int i = 0; i < BufferCount; i++) { WaveInHeaders[i] = (Win32.WAVEHDR*)Marshal.AllocHGlobal(sizeof(Win32.WAVEHDR)); WaveInHeaders[i]->dwLoops = 0; WaveInHeaders[i]->dwUser = IntPtr.Zero; WaveInHeaders[i]->lpNext = IntPtr.Zero; WaveInHeaders[i]->reserved = IntPtr.Zero; WaveInHeaders[i]->lpData = Marshal.AllocHGlobal(BufferSize); WaveInHeaders[i]->dwBufferLength = (uint)BufferSize; WaveInHeaders[i]->dwBytesRecorded = 0; WaveInHeaders[i]->dwFlags = 0; Win32.MMRESULT hr = Win32.waveInPrepareHeader(hWaveIn, WaveInHeaders[i], sizeof(Win32.WAVEHDR)); if (hr == Win32.MMRESULT.MMSYSERR_NOERROR) { if (i == 0) { hr = Win32.waveInAddBuffer(hWaveIn, WaveInHeaders[i], sizeof(Win32.WAVEHDR)); } createdHeaders++; } } return (createdHeaders == BufferCount); } private void FreeWaveInHeaders() { try { if (WaveInHeaders != null) { for (int i = 0; i < WaveInHeaders.Length; i++) { Win32.MMRESULT hr = Win32.waveInUnprepareHeader(hWaveIn, WaveInHeaders[i], sizeof(Win32.WAVEHDR)); int count = 0; while (count <= 100 && (WaveInHeaders[i]->dwFlags & Win32.WaveHdrFlags.WHDR_INQUEUE) == Win32.WaveHdrFlags.WHDR_INQUEUE) { Thread.Sleep(20); count++; } if ((WaveInHeaders[i]->dwFlags & Win32.WaveHdrFlags.WHDR_INQUEUE) != Win32.WaveHdrFlags.WHDR_INQUEUE) { if (WaveInHeaders[i]->lpData != IntPtr.Zero) { Marshal.FreeHGlobal(WaveInHeaders[i]->lpData); WaveInHeaders[i]->lpData = IntPtr.Zero; } } } } } catch (Exception ex) { System.Diagnostics.Debug.Write(ex.Message); } } private void StartThreadRecording() { if (Started == false) { ThreadRecording = new Thread(new ThreadStart(OnThreadRecording)); IsThreadRecordingRunning = true; ThreadRecording.Name = "Recording"; ThreadRecording.Priority = ThreadPriority.Highest; ThreadRecording.Start(); } } private bool OpenWaveIn() { if (hWaveIn == IntPtr.Zero) { if (IsWaveInOpened == false) { Win32.WAVEFORMATEX waveFormatEx = new() { wFormatTag = (ushort)Win32.WaveFormatFlags.WAVE_FORMAT_PCM, nChannels = (ushort)Channels, nSamplesPerSec = (ushort)SamplesPerSecond, wBitsPerSample = (ushort)BitsPerSample }; waveFormatEx.nBlockAlign = (ushort)((waveFormatEx.wBitsPerSample * waveFormatEx.nChannels) >> 3); waveFormatEx.nAvgBytesPerSec = waveFormatEx.nBlockAlign * waveFormatEx.nSamplesPerSec; int deviceId = WinSound.GetWaveInDeviceIdByName(WaveInDeviceName); Win32.MMRESULT hr = Win32.waveInOpen(ref hWaveIn, deviceId, ref waveFormatEx, delegateWaveInProc, 0, (int)Win32.WaveProcFlags.CALLBACK_FUNCTION); if (hWaveIn == IntPtr.Zero) { IsWaveInOpened = false; return false; } GCHandle.Alloc(hWaveIn, GCHandleType.Pinned); } } IsWaveInOpened = true; return true; } internal bool Start(string waveInDeviceName, int samplesPerSecond, int bitsPerSample, int channels, int bufferCount, int bufferSize) { try { lock (Locker) { if (Started == false) { WaveInDeviceName = waveInDeviceName; SamplesPerSecond = samplesPerSecond; BitsPerSample = bitsPerSample; Channels = channels; BufferCount = bufferCount; BufferSize = bufferSize; if (OpenWaveIn()) { if (CreateWaveInHeaders()) { Win32.MMRESULT hr = Win32.waveInStart(hWaveIn); if (hr == Win32.MMRESULT.MMSYSERR_NOERROR) { IsWaveInStarted = true; StartThreadRecording(); Stopped = false; return true; } else { return false; } } } } return false; } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(string.Format("Start | {0}", ex.Message)); return false; } } internal bool Stop() { try { lock (Locker) { if (Started) { Stopped = true; IsThreadRecordingRunning = false; CloseWaveIn(); AutoResetEventDataRecorded.Set(); return true; } return false; } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(string.Format("Stop | {0}", ex.Message)); return false; } } private void CloseWaveIn() { Win32.MMRESULT hr = Win32.waveInStop(hWaveIn); int resetCount = 0; while (IsAnyWaveInHeaderInState(Win32.WaveHdrFlags.WHDR_INQUEUE) & resetCount < 20) { hr = Win32.waveInReset(hWaveIn); Thread.Sleep(50); resetCount++; } FreeWaveInHeaders(); hr = Win32.waveInClose(hWaveIn); } private bool IsAnyWaveInHeaderInState(Win32.WaveHdrFlags state) { for (int i = 0; i < WaveInHeaders.Length; i++) { if ((WaveInHeaders[i]->dwFlags & state) == state) { return true; } } return false; } private void WaveInProc(IntPtr hWaveIn, Win32.WIM_Messages msg, IntPtr dwInstance, Win32.WAVEHDR* pWaveHdr, IntPtr lParam) { switch (msg) { //Open case Win32.WIM_Messages.OPEN: break; //Data case Win32.WIM_Messages.DATA: IsDataIncomming = true; CurrentRecordedHeader = pWaveHdr; AutoResetEventDataRecorded.Set(); break; //Close case Win32.WIM_Messages.CLOSE: IsDataIncomming = false; IsWaveInOpened = false; AutoResetEventDataRecorded.Set(); this.hWaveIn = IntPtr.Zero; break; } } private void OnThreadRecording() { while (Started && !Stopped) { AutoResetEventDataRecorded.WaitOne(); try { if (Started && !Stopped) { if (CurrentRecordedHeader->dwBytesRecorded > 0) { if (DataRecorded != null && IsDataIncomming) { try { byte[] bytes = new byte[CurrentRecordedHeader->dwBytesRecorded]; Marshal.Copy(CurrentRecordedHeader->lpData, bytes, 0, (int)CurrentRecordedHeader->dwBytesRecorded); DataRecorded(bytes); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(string.Format("Recorder.cs | OnThreadRecording() | {0}", ex.Message)); } } for (int i = 0; i < WaveInHeaders.Length; i++) { if ((WaveInHeaders[i]->dwFlags & Win32.WaveHdrFlags.WHDR_INQUEUE) == 0) { Win32.MMRESULT hr = Win32.waveInAddBuffer(hWaveIn, WaveInHeaders[i], sizeof(Win32.WAVEHDR)); } } } } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } } lock (Locker) { IsWaveInStarted = false; IsThreadRecordingRunning = false; } if (RecordingStopped != null) { try { RecordingStopped(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(string.Format("Recording Stopped | {0}", ex.Message)); } } } } }