Luski.Net/Luski.net/Sound/Player.cs

418 lines
14 KiB
C#
Raw Normal View History

2023-01-01 22:50:39 -05:00
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));
}
}
}
}
}