Files
Vocaluxe/Vocaluxe/Lib/Sound/CPortAudioPlay.cs
T

958 lines
25 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using PortAudioSharp;
using Vocaluxe.Base;
using Vocaluxe.Lib.Sound.Decoder;
namespace Vocaluxe.Lib.Sound
{
class CPortAudioPlay : IPlayback
{
private bool _Initialized = false;
private List<PortAudioStream> _Decoder = new List<PortAudioStream>();
private CLOSEPROC closeproc;
private int _Count = 1;
private Object MutexDecoder = new Object();
private List<AudioStreams> _Streams;
public CPortAudioPlay()
{
Init();
}
public bool Init()
{
if (_Initialized)
CloseAll();
closeproc = new CLOSEPROC(close_proc);
_Initialized = true;
_Streams = new List<AudioStreams>();
return true;
}
public void CloseAll()
{
lock (MutexDecoder)
{
for (int i = 0; i < _Decoder.Count; i++)
{
_Decoder[i].Free(closeproc, i + 1);
}
}
}
public void SetGlobalVolume(float Volume)
{
if (_Initialized)
{
//Bass.BASS_SetVolume(Volume / 100f);
}
}
public int GetStreamCount()
{
if (!_Initialized)
return 0;
lock (MutexDecoder)
{
return _Streams.Count;
}
}
public void Update()
{
}
#region Stream Handling
public int Load(string Media)
{
return Load(Media, false);
}
public int Load(string Media, bool Prescan)
{
AudioStreams stream = new AudioStreams(0);
PortAudioStream decoder = new PortAudioStream();
if (decoder.Open(Media) > -1)
{
lock (MutexDecoder)
{
_Decoder.Add(decoder);
stream.handle = _Count++;
stream.file = Media;
_Streams.Add(stream);
return stream.handle;
}
}
return 0;
}
public void Close(int Stream)
{
if (_Initialized)
{
lock (MutexDecoder)
{
if (AlreadyAdded(Stream))
{
_Decoder[GetStreamIndex(Stream)].Free(closeproc, Stream);
}
}
}
}
public void Play(int Stream)
{
Play(Stream, false);
}
public void Play(int Stream, bool Loop)
{
if (_Initialized)
{
lock (MutexDecoder)
{
if (AlreadyAdded(Stream))
{
_Decoder[GetStreamIndex(Stream)].Loop = Loop;
_Decoder[GetStreamIndex(Stream)].Play();
}
}
}
}
public void Pause(int Stream)
{
if (_Initialized)
{
lock (MutexDecoder)
{
if (AlreadyAdded(Stream))
{
_Decoder[GetStreamIndex(Stream)].Paused = true;
}
}
}
}
public void Stop(int Stream)
{
Pause(Stream);
}
public void Fade(int Stream, float TargetVolume, float Seconds)
{
if (_Initialized)
{
lock (MutexDecoder)
{
if (AlreadyAdded(Stream))
{
_Decoder[GetStreamIndex(Stream)].Fade(TargetVolume, Seconds);
}
}
}
}
public void FadeAndPause(int Stream, float TargetVolume, float Seconds)
{
if (_Initialized)
{
lock (MutexDecoder)
{
if (AlreadyAdded(Stream))
{
_Decoder[GetStreamIndex(Stream)].FadeAndPause(TargetVolume, Seconds);
}
}
}
}
public void FadeAndStop(int Stream, float TargetVolume, float Seconds)
{
if (_Initialized)
{
lock (MutexDecoder)
{
if (AlreadyAdded(Stream))
{
_Decoder[GetStreamIndex(Stream)].FadeAndStop(TargetVolume, Seconds, closeproc, Stream);
}
}
}
}
public void SetStreamVolume(int Stream, float Volume)
{
if (_Initialized)
{
lock (MutexDecoder)
{
if (AlreadyAdded(Stream))
{
_Decoder[GetStreamIndex(Stream)].Volume = Volume;
}
}
}
}
public void SetStreamVolumeMax(int Stream, float Volume)
{
if (_Initialized)
{
lock (MutexDecoder)
{
if (AlreadyAdded(Stream))
{
_Decoder[GetStreamIndex(Stream)].VolumeMax = Volume;
}
}
}
}
public float GetLength(int Stream)
{
if (_Initialized)
{
lock (MutexDecoder)
{
if (AlreadyAdded(Stream))
{
return _Decoder[GetStreamIndex(Stream)].Length;
}
}
}
return 0f;
}
public float GetPosition(int Stream)
{
if (_Initialized)
{
lock (MutexDecoder)
{
if (AlreadyAdded(Stream))
{
return _Decoder[GetStreamIndex(Stream)].Position;
}
}
return 0f;
}
return 0f;
}
public bool IsPlaying(int Stream)
{
if (_Initialized)
{
lock (MutexDecoder)
{
if (AlreadyAdded(Stream))
{
return !_Decoder[GetStreamIndex(Stream)].Paused && !_Decoder[GetStreamIndex(Stream)].Finished;
}
}
}
return false;
}
public bool IsPaused(int Stream)
{
if (_Initialized)
{
lock (MutexDecoder)
{
if (AlreadyAdded(Stream))
{
return _Decoder[GetStreamIndex(Stream)].Paused;
}
}
}
return false;
}
public bool IsFinished(int Stream)
{
if (_Initialized)
{
lock (MutexDecoder)
{
if (AlreadyAdded(Stream))
{
return _Decoder[GetStreamIndex(Stream)].Finished;
}
}
}
return true;
}
public void SetPosition(int Stream, float Position)
{
if (_Initialized)
{
lock (MutexDecoder)
{
if (AlreadyAdded(Stream))
{
_Decoder[GetStreamIndex(Stream)].Skip(Position);
}
}
}
}
#endregion Stream Handling
private bool AlreadyAdded(int Stream)
{
foreach (AudioStreams st in _Streams)
{
if (st.handle == Stream)
{
return true;
}
}
return false;
}
private int GetStreamIndex(int Stream)
{
for (int i = 0; i < _Streams.Count; i++)
{
if (_Streams[i].handle == Stream)
return i;
}
return -1;
}
private void EndSync(int handle, int Stream, int data, IntPtr user)
{
if (_Initialized)
{
if (AlreadyAdded(Stream))
{
Close(Stream);
}
}
}
private void close_proc(int StreamID)
{
if (_Initialized)
{
lock (MutexDecoder)
{
if (AlreadyAdded(StreamID))
{
int Index = GetStreamIndex(StreamID);
_Decoder.RemoveAt(Index);
_Streams.RemoveAt(Index);
}
}
}
}
}
class PortAudioStream
{
const long BUFSIZE = 1000000L;
const long BEGINREFILL = 800000L;
private CSyncTimer _SyncTimer;
private bool _Initialized;
private int _ByteCount = 4;
private float _Volume = 1f;
private float _VolumeMax = 1f;
private Stopwatch _fadeTimer = new Stopwatch();
private float _fadeTime = 0f;
private float _targetVolume = 1f;
private float _startVolume = 1f;
private bool _closeStreamAfterFade = false;
private bool _pauseStreamAfterFade = false;
private bool _fading = false;
private PortAudio.PaHostApiInfo _apiInfo;
private PortAudio.PaDeviceInfo _outputDeviceInfo;
private IntPtr _Ptr = new IntPtr(0);
private Stopwatch _FadeTimer = new Stopwatch();
private CLOSEPROC _Closeproc;
private PortAudio.PaStreamCallbackDelegate _paStreamCallback;
private int _StreamID;
private string _FileName;
private IAudioDecoder _Decoder;
private float _BytesPerSecond;
private bool _NoMoreData = false;
private bool _FileOpened = false;
private bool _waiting = false;
private bool _skip = false;
private bool _Loop = false;
private float _Duration = 0f;
private float _CurrentTime = 0f;
private float _TimeCode = 0f;
private bool _Paused = false;
private RingBuffer _data;
private float _SetStart = 0f;
private float _Start = 0f;
private bool _SetLoop = false;
private bool _SetSkip = false;
private bool _terminated = false;
private Thread _DecoderThread;
private AutoResetEvent EventDecode = new AutoResetEvent(false);
private Object _LockData = new Object();
private Object _LockSyncSignals = new Object();
public PortAudioStream()
{
_Initialized = false;
_SyncTimer = new CSyncTimer(0f, 1f, 0.02f);
_DecoderThread = new Thread(Execute);
}
public void Free(CLOSEPROC close_proc, int StreamID)
{
_Closeproc = close_proc;
_StreamID = StreamID;
_terminated = true;
}
public float Length
{
get
{
if (_FileOpened)
return _Duration;
return 0f;
}
}
public bool Finished
{
get
{
lock (_LockData)
{
return _NoMoreData && _data.BytesNotRead == 0L && _SyncTimer.Time >= _Duration;
}
}
}
public float Volume
{
get { return _Volume * 100f; }
set
{
lock (_LockData)
{
_Volume = value / 100f;
if (_Volume < 0f)
_Volume = 0f;
if (_Volume > 1f)
_Volume = 1f;
}
}
}
public float VolumeMax
{
get { return _VolumeMax * 100f; }
set
{
lock (_LockData)
{
_VolumeMax = value / 100f;
if (_VolumeMax < 0f)
_VolumeMax = 0f;
if (_VolumeMax > 1f)
_VolumeMax = 1f;
}
}
}
public float Position
{
get
{
lock (_LockData)
{
if (Finished)
_SyncTimer.Pause();
return _SyncTimer.Time;
}
}
}
public bool Paused
{
get { return _Paused; }
set
{
_Paused = value;
lock (_LockData)
{
if (_Paused)
_SyncTimer.Pause();
else
{
_SyncTimer.Resume();
EventDecode.Set();
}
}
}
}
public void Fade(float TargetVolume, float FadeTime)
{
_fading = true;
_fadeTimer.Stop();
_fadeTimer.Reset();
_startVolume = _Volume;
_targetVolume = TargetVolume / 100f;
_fadeTime = FadeTime;
_fadeTimer.Start();
}
public void FadeAndPause(float TargetVolume, float FadeTime)
{
_pauseStreamAfterFade = true;
Fade(TargetVolume, FadeTime);
}
public void FadeAndStop(float TargetVolume, float FadeTime, CLOSEPROC close_proc, int StreamID)
{
_Closeproc = close_proc;
_StreamID = StreamID;
_closeStreamAfterFade = true;
Fade(TargetVolume, FadeTime);
}
public void Play()
{
Paused = false;
_pauseStreamAfterFade = false;
errorCheck("StartStream", PortAudio.Pa_StartStream(_Ptr));
}
public void Stop()
{
errorCheck("StartStream", PortAudio.Pa_StopStream(_Ptr));
Skip(0f);
}
public bool Loop
{
get { return _SetLoop; }
set { _SetLoop = value; }
}
public int Open(string FileName)
{
if (_FileOpened)
return -1;
if (!System.IO.File.Exists(FileName))
return -1;
if (_FileOpened)
return -1;
_Decoder = new CAudioDecoderFFmpeg();
_Decoder.Init();
try
{
if (errorCheck("Initialize", PortAudio.Pa_Initialize()))
return -1;
_Initialized = true;
int hostApi = apiSelect();
_apiInfo = PortAudio.Pa_GetHostApiInfo(hostApi);
_outputDeviceInfo = PortAudio.Pa_GetDeviceInfo(_apiInfo.defaultOutputDevice);
_paStreamCallback = new PortAudio.PaStreamCallbackDelegate(_PaStreamCallback);
if (_outputDeviceInfo.defaultLowOutputLatency < 0.1)
_outputDeviceInfo.defaultLowOutputLatency = 0.1;
}
catch (Exception)
{
_Initialized = false;
CLog.LogError("Error Init PortAudio Playback");
return -1;
}
_FileName = FileName;
_Decoder.Open(FileName);
_Duration = _Decoder.GetLength();
FormatInfo format = _Decoder.GetFormatInfo();
_ByteCount = 2 * format.ChannelCount;
_BytesPerSecond = format.SamplesPerSecond * _ByteCount;
_CurrentTime = 0f;
_SyncTimer.Time = _CurrentTime;
AudioStreams stream = new AudioStreams(0);
IntPtr data = new IntPtr(0);
PortAudio.PaStreamParameters outputParams = new PortAudio.PaStreamParameters();
outputParams.channelCount = format.ChannelCount;
outputParams.device = _apiInfo.defaultOutputDevice;
outputParams.sampleFormat = PortAudio.PaSampleFormat.paInt16;
outputParams.suggestedLatency = _outputDeviceInfo.defaultLowOutputLatency;
uint bufsize = (uint)CConfig.AudioBufferSize;
errorCheck("OpenDefaultStream (playback)", PortAudio.Pa_OpenStream(
out _Ptr,
IntPtr.Zero,
ref outputParams,
format.SamplesPerSecond,
bufsize,
PortAudio.PaStreamFlags.paNoFlag,
_paStreamCallback,
data));
stream.handle = _Ptr.ToInt32();
if (stream.handle != 0)
{
_Paused = true;
_waiting = true;
_FileOpened = true;
_data = new RingBuffer(BUFSIZE);
_NoMoreData = false;
_DecoderThread.Priority = ThreadPriority.Normal;
_DecoderThread.Name = Path.GetFileName(FileName);
_DecoderThread.Start();
return stream.handle;
}
return -1;
}
public bool Skip(float Time)
{
lock (_LockSyncSignals)
{
_SetStart = Time;
_SetSkip = true;
_waiting = true;
}
return true;
}
#region Threading
private void DoSkip()
{
lock (_LockData)
{
_Decoder.SetPosition(_Start);
_CurrentTime = _Start;
_TimeCode = _Start;
_data = new RingBuffer(BUFSIZE);
_NoMoreData = false;
EventDecode.Set();
_waiting = false;
_SyncTimer.Time = _Start;
}
}
private void Execute()
{
while (!_terminated)
{
if (EventDecode.WaitOne(10))
{
lock (_LockSyncSignals)
{
if (_SetSkip)
{
_skip = true;
EventDecode.Set();
_waiting = true;
}
_SetSkip = false;
_Start = _SetStart;
_Loop = _SetLoop;
}
if (_skip)
{
DoSkip();
_skip = false;
}
if (!_waiting)
{
DoDecode();
Update();
}
}
}
DoFree();
}
private void DoDecode()
{
if (!_FileOpened)
return;
if (_Paused)
return;
if (_terminated)
return;
float Timecode;
byte[] Buffer;
bool DoIt = false;
lock (_LockData)
{
if (!_skip && BEGINREFILL > _data.BytesNotRead)
DoIt = true;
}
if (!DoIt)
return;
_Decoder.Decode(out Buffer, out Timecode);
if (Buffer == null)
{
if (_Loop)
{
lock (_LockSyncSignals)
{
_CurrentTime = 0f;
_Start = 0f;
}
DoSkip();
}
else
{
_NoMoreData = true;
}
return;
}
lock (_LockData)
{
_data.Write(Buffer);
_TimeCode = Timecode;
if (_data.BytesNotRead < BEGINREFILL)
{
_waiting = false;
EventDecode.Set();
}
else
_waiting = true;
}
}
private void DoFree()
{
if (_Initialized)
{
Stop();
PortAudio.Pa_Terminate();
}
_Closeproc(_StreamID);
}
#endregion Threading
#region Callbacks
private PortAudio.PaStreamCallbackResult _PaStreamCallback(
IntPtr input,
IntPtr output,
uint frameCount,
ref PortAudio.PaStreamCallbackTimeInfo timeInfo,
PortAudio.PaStreamCallbackFlags statusFlags,
IntPtr userData)
{
byte[] buf = new byte[frameCount * _ByteCount];
if (_Paused)
{
try
{
Marshal.Copy(buf, 0, output, (int)frameCount * _ByteCount);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
return PortAudio.PaStreamCallbackResult.paContinue;
}
lock (_LockData)
{
if (_NoMoreData || _data.BytesNotRead >= buf.Length)
{
_data.Read(ref buf);
byte[] b = new byte[2];
for (int i = 0; i < buf.Length; i += _ByteCount)
{
b[0] = buf[i];
b[1] = buf[i + 1];
b = BitConverter.GetBytes((Int16)(BitConverter.ToInt16(b, 0) * _Volume * _VolumeMax));
buf[i] = b[0];
buf[i + 1] = b[1];
if (_ByteCount == 4)
{
b[0] = buf[i + 2];
b[1] = buf[i + 3];
b = BitConverter.GetBytes((Int16)(BitConverter.ToInt16(b, 0) * _Volume * _VolumeMax));
buf[i + 2] = b[0];
buf[i + 3] = b[1];
}
}
}
if (_data.BytesNotRead < BEGINREFILL)
{
EventDecode.Set();
_waiting = false;
}
else
_waiting = true;
float latency = buf.Length / _BytesPerSecond + CConfig.AudioLatency/1000f;
float time = _TimeCode - _data.BytesNotRead / _BytesPerSecond - latency;
if (!_NoMoreData)
_CurrentTime = _SyncTimer.Update(time);
}
try
{
Marshal.Copy(buf, 0, output, (int)frameCount * _ByteCount);
}
catch (Exception e)
{
CLog.LogError("Error PortAudio.StreamCallback: " + e.Message);
}
return PortAudio.PaStreamCallbackResult.paContinue;
}
#endregion Callbacks
private bool errorCheck(String action, PortAudio.PaError errorCode)
{
if (errorCode != PortAudio.PaError.paNoError)
{
if (errorCode == PortAudio.PaError.paStreamIsNotStopped)
return false;
CLog.LogError(action + " error (playback): " + PortAudio.Pa_GetErrorText(errorCode));
if (errorCode == PortAudio.PaError.paUnanticipatedHostError)
{
PortAudio.PaHostErrorInfo errorInfo = PortAudio.Pa_GetLastHostErrorInfo();
CLog.LogError("- Host error API type: " + errorInfo.hostApiType);
CLog.LogError("- Host error code: " + errorInfo.errorCode);
CLog.LogError("- Host error text: " + errorInfo.errorText);
}
return true;
}
return false;
}
private int apiSelect()
{
if (!_Initialized)
return 0;
int selectedHostApi = PortAudio.Pa_GetDefaultHostApi();
int apiCount = PortAudio.Pa_GetHostApiCount();
for (int i = 0; i < apiCount; i++)
{
PortAudio.PaHostApiInfo apiInfo = PortAudio.Pa_GetHostApiInfo(i);
if ((apiInfo.type == PortAudio.PaHostApiTypeId.paDirectSound)
|| (apiInfo.type == PortAudio.PaHostApiTypeId.paALSA))
selectedHostApi = i;
}
return selectedHostApi;
}
private void Update()
{
if (_fading)
{
if ((float)_fadeTimer.ElapsedMilliseconds / 1000f < _fadeTime)
_Volume = _startVolume + (_targetVolume - _startVolume) * ((_fadeTimer.ElapsedMilliseconds / 1000f) / _fadeTime);
else
{
_Volume = _targetVolume;
_fadeTimer.Stop();
_fading = false;
if (_closeStreamAfterFade)
_terminated = true;
if (_pauseStreamAfterFade)
Paused = true;
}
}
}
}
}