418 lines
14 KiB
C#
Executable File
418 lines
14 KiB
C#
Executable File
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
|
|
namespace Luski.net.Sound
|
|
{
|
|
internal unsafe class Player
|
|
{
|
|
internal Player()
|
|
{
|
|
|
|
delegateWaveOutProc = new Win32.DelegateWaveOutProc(WaveOutProc);
|
|
}
|
|
|
|
private readonly LockerClass Locker = new();
|
|
private readonly LockerClass LockerCopy = new();
|
|
private IntPtr hWaveOut = IntPtr.Zero;
|
|
private string WaveOutDeviceName = "";
|
|
private bool IsWaveOutOpened = false;
|
|
private bool IsThreadPlayWaveOutRunning = false;
|
|
private bool IsClosed = false;
|
|
private bool IsPaused = false;
|
|
private bool IsStarted = false;
|
|
private bool IsBlocking = false;
|
|
private int SamplesPerSecond = 8000;
|
|
private int BitsPerSample = 16;
|
|
private int Channels = 1;
|
|
private int BufferCount = 8;
|
|
private readonly int BufferLength = 1024;
|
|
private Win32.WAVEHDR*[] WaveOutHeaders;
|
|
private readonly Win32.DelegateWaveOutProc delegateWaveOutProc;
|
|
private Thread? ThreadPlayWaveOut;
|
|
private readonly AutoResetEvent AutoResetEventDataPlayed = new(false);
|
|
|
|
internal delegate void DelegateStopped();
|
|
internal event DelegateStopped? PlayerClosed;
|
|
internal event DelegateStopped? PlayerStopped;
|
|
|
|
internal bool Opened => IsWaveOutOpened & IsClosed == false;
|
|
|
|
internal bool Playing
|
|
{
|
|
get
|
|
{
|
|
if (Opened && IsStarted)
|
|
{
|
|
foreach (Win32.WAVEHDR* pHeader in WaveOutHeaders)
|
|
{
|
|
if (IsHeaderInqueue(*pHeader))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private bool CreateWaveOutHeaders()
|
|
{
|
|
WaveOutHeaders = new Win32.WAVEHDR*[BufferCount];
|
|
int createdHeaders = 0;
|
|
|
|
for (int i = 0; i < BufferCount; i++)
|
|
{
|
|
WaveOutHeaders[i] = (Win32.WAVEHDR*)Marshal.AllocHGlobal(sizeof(Win32.WAVEHDR));
|
|
|
|
WaveOutHeaders[i]->dwLoops = 0;
|
|
WaveOutHeaders[i]->dwUser = IntPtr.Zero;
|
|
WaveOutHeaders[i]->lpNext = IntPtr.Zero;
|
|
WaveOutHeaders[i]->reserved = IntPtr.Zero;
|
|
WaveOutHeaders[i]->lpData = Marshal.AllocHGlobal(BufferLength);
|
|
WaveOutHeaders[i]->dwBufferLength = (uint)BufferLength;
|
|
WaveOutHeaders[i]->dwBytesRecorded = 0;
|
|
WaveOutHeaders[i]->dwFlags = 0;
|
|
|
|
Win32.MMRESULT hr = Win32.waveOutPrepareHeader(hWaveOut, WaveOutHeaders[i], sizeof(Win32.WAVEHDR));
|
|
if (hr == Win32.MMRESULT.MMSYSERR_NOERROR)
|
|
{
|
|
createdHeaders++;
|
|
}
|
|
}
|
|
|
|
return (createdHeaders == BufferCount);
|
|
}
|
|
|
|
private void FreeWaveOutHeaders()
|
|
{
|
|
try
|
|
{
|
|
if (WaveOutHeaders != null)
|
|
{
|
|
for (int i = 0; i < WaveOutHeaders.Length; i++)
|
|
{
|
|
Win32.MMRESULT hr = Win32.waveOutUnprepareHeader(hWaveOut, WaveOutHeaders[i], sizeof(Win32.WAVEHDR));
|
|
|
|
int count = 0;
|
|
while (count <= 100 && (WaveOutHeaders[i]->dwFlags & Win32.WaveHdrFlags.WHDR_INQUEUE) == Win32.WaveHdrFlags.WHDR_INQUEUE)
|
|
{
|
|
Thread.Sleep(20);
|
|
count++;
|
|
}
|
|
|
|
if ((WaveOutHeaders[i]->dwFlags & Win32.WaveHdrFlags.WHDR_INQUEUE) != Win32.WaveHdrFlags.WHDR_INQUEUE)
|
|
{
|
|
if (WaveOutHeaders[i]->lpData != IntPtr.Zero)
|
|
{
|
|
Marshal.FreeHGlobal(WaveOutHeaders[i]->lpData);
|
|
WaveOutHeaders[i]->lpData = IntPtr.Zero;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.Write(ex.Message);
|
|
}
|
|
}
|
|
|
|
private void StartThreadPlayWaveOut()
|
|
{
|
|
if (IsThreadPlayWaveOutRunning == false)
|
|
{
|
|
ThreadPlayWaveOut = new System.Threading.Thread(new System.Threading.ThreadStart(OnThreadPlayWaveOut));
|
|
IsThreadPlayWaveOutRunning = true;
|
|
ThreadPlayWaveOut.Name = "PlayWaveOut";
|
|
ThreadPlayWaveOut.Priority = System.Threading.ThreadPriority.Highest;
|
|
ThreadPlayWaveOut.Start();
|
|
}
|
|
}
|
|
|
|
private bool OpenWaveOut()
|
|
{
|
|
if (hWaveOut == IntPtr.Zero)
|
|
{
|
|
if (IsWaveOutOpened == 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.GetWaveOutDeviceIdByName(WaveOutDeviceName);
|
|
Win32.MMRESULT hr = Win32.waveOutOpen(ref hWaveOut, deviceId, ref waveFormatEx, delegateWaveOutProc, 0, (int)Win32.WaveProcFlags.CALLBACK_FUNCTION);
|
|
|
|
if (hr != Win32.MMRESULT.MMSYSERR_NOERROR)
|
|
{
|
|
IsWaveOutOpened = false;
|
|
return false;
|
|
}
|
|
|
|
GCHandle.Alloc(hWaveOut, GCHandleType.Pinned);
|
|
}
|
|
}
|
|
|
|
IsWaveOutOpened = true;
|
|
return true;
|
|
}
|
|
|
|
internal bool Open(string waveOutDeviceName, int samplesPerSecond, int bitsPerSample, int channels, int bufferCount)
|
|
{
|
|
try
|
|
{
|
|
lock (Locker)
|
|
{
|
|
if (Opened == false)
|
|
{
|
|
|
|
WaveOutDeviceName = waveOutDeviceName;
|
|
SamplesPerSecond = samplesPerSecond;
|
|
BitsPerSample = bitsPerSample;
|
|
Channels = channels;
|
|
BufferCount = Math.Max(bufferCount, 1);
|
|
|
|
if (OpenWaveOut())
|
|
{
|
|
if (CreateWaveOutHeaders())
|
|
{
|
|
StartThreadPlayWaveOut();
|
|
IsClosed = false;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine(string.Format("Start | {0}", ex.Message));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal bool PlayData(byte[] datas, bool isBlocking)
|
|
{
|
|
try
|
|
{
|
|
if (Opened)
|
|
{
|
|
int index = GetNextFreeWaveOutHeaderIndex();
|
|
if (index != -1)
|
|
{
|
|
IsBlocking = isBlocking;
|
|
|
|
if (WaveOutHeaders[index]->dwBufferLength != datas.Length)
|
|
{
|
|
Marshal.FreeHGlobal(WaveOutHeaders[index]->lpData);
|
|
WaveOutHeaders[index]->lpData = Marshal.AllocHGlobal(datas.Length);
|
|
WaveOutHeaders[index]->dwBufferLength = (uint)datas.Length;
|
|
}
|
|
|
|
WaveOutHeaders[index]->dwBufferLength = (uint)datas.Length;
|
|
WaveOutHeaders[index]->dwUser = (IntPtr)index;
|
|
Marshal.Copy(datas, 0, WaveOutHeaders[index]->lpData, datas.Length);
|
|
|
|
IsStarted = true;
|
|
Win32.MMRESULT hr = Win32.waveOutWrite(hWaveOut, WaveOutHeaders[index], sizeof(Win32.WAVEHDR));
|
|
if (hr == Win32.MMRESULT.MMSYSERR_NOERROR)
|
|
{
|
|
if (isBlocking)
|
|
{
|
|
AutoResetEventDataPlayed.WaitOne();
|
|
AutoResetEventDataPlayed.Set();
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
AutoResetEventDataPlayed.Set();
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
System.Diagnostics.Debug.WriteLine(string.Format("No free WaveOut Buffer found | {0}", DateTime.Now.ToLongTimeString()));
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine(string.Format("PlayData | {0}", ex.Message));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal bool Close()
|
|
{
|
|
try
|
|
{
|
|
lock (Locker)
|
|
{
|
|
if (Opened)
|
|
{
|
|
IsClosed = true;
|
|
|
|
int count = 0;
|
|
while (Win32.waveOutReset(hWaveOut) != Win32.MMRESULT.MMSYSERR_NOERROR && count <= 100)
|
|
{
|
|
Thread.Sleep(50);
|
|
count++;
|
|
}
|
|
|
|
FreeWaveOutHeaders();
|
|
|
|
count = 0;
|
|
while (Win32.waveOutClose(hWaveOut) != Win32.MMRESULT.MMSYSERR_NOERROR && count <= 100)
|
|
{
|
|
Thread.Sleep(50);
|
|
count++;
|
|
}
|
|
|
|
IsWaveOutOpened = false;
|
|
AutoResetEventDataPlayed.Set();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine(string.Format("Close | {0}", ex.Message));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private int GetNextFreeWaveOutHeaderIndex()
|
|
{
|
|
for (int i = 0; i < WaveOutHeaders.Length; i++)
|
|
{
|
|
if (IsHeaderPrepared(*WaveOutHeaders[i]) && !IsHeaderInqueue(*WaveOutHeaders[i]))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
private static bool IsHeaderPrepared(Win32.WAVEHDR header)
|
|
{
|
|
return (header.dwFlags & Win32.WaveHdrFlags.WHDR_PREPARED) > 0;
|
|
}
|
|
|
|
private static bool IsHeaderInqueue(Win32.WAVEHDR header)
|
|
{
|
|
return (header.dwFlags & Win32.WaveHdrFlags.WHDR_INQUEUE) > 0;
|
|
}
|
|
|
|
private void WaveOutProc(IntPtr hWaveOut, Win32.WOM_Messages msg, IntPtr dwInstance, Win32.WAVEHDR* pWaveHeader, IntPtr lParam)
|
|
{
|
|
try
|
|
{
|
|
switch (msg)
|
|
{
|
|
//Open
|
|
case Win32.WOM_Messages.OPEN:
|
|
break;
|
|
|
|
//Done
|
|
case Win32.WOM_Messages.DONE:
|
|
IsStarted = true;
|
|
AutoResetEventDataPlayed.Set();
|
|
break;
|
|
|
|
//Close
|
|
case Win32.WOM_Messages.CLOSE:
|
|
IsStarted = false;
|
|
IsWaveOutOpened = false;
|
|
IsPaused = false;
|
|
IsClosed = true;
|
|
AutoResetEventDataPlayed.Set();
|
|
hWaveOut = IntPtr.Zero;
|
|
break;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine(string.Format("Player.cs | waveOutProc() | {0}", ex.Message));
|
|
AutoResetEventDataPlayed.Set();
|
|
}
|
|
}
|
|
|
|
private void OnThreadPlayWaveOut()
|
|
{
|
|
while (Opened && !IsClosed)
|
|
{
|
|
AutoResetEventDataPlayed.WaitOne();
|
|
|
|
lock (Locker)
|
|
{
|
|
if (Opened && !IsClosed)
|
|
{
|
|
IsThreadPlayWaveOutRunning = true;
|
|
|
|
if (!Playing)
|
|
{
|
|
if (IsStarted)
|
|
{
|
|
IsStarted = false;
|
|
if (PlayerStopped != null)
|
|
{
|
|
try
|
|
{
|
|
PlayerStopped();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine(string.Format("Player Stopped | {0}", ex.Message));
|
|
}
|
|
finally
|
|
{
|
|
AutoResetEventDataPlayed.Set();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsBlocking)
|
|
{
|
|
AutoResetEventDataPlayed.Set();
|
|
}
|
|
}
|
|
|
|
lock (Locker)
|
|
{
|
|
IsThreadPlayWaveOutRunning = false;
|
|
}
|
|
|
|
if (PlayerClosed != null)
|
|
{
|
|
try
|
|
{
|
|
PlayerClosed();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine(string.Format("Player Closed | {0}", ex.Message));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|