Split classes of Lib/Sound
This commit is contained in:
1 parent
052e32aa6c
commit
312c28ebed
8 files changed
+1606
-795
No files matched your search
@@ -0,0 +1,288 @@
|
||||
using System;
|
||||
using GLib;
|
||||
using Gst;
|
||||
using Vocaluxe.Base;
|
||||
using VocaluxeLib;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp
|
||||
{
|
||||
public class CGstreamerSharpAudioStream
|
||||
{
|
||||
private Element _Element;
|
||||
private bool _Loop;
|
||||
public volatile bool Closed = true;
|
||||
public volatile bool Finished;
|
||||
|
||||
private CFading _Fading;
|
||||
private EStreamAction _AfterFadeAction;
|
||||
private float _Volume = 1f;
|
||||
|
||||
private float _MaxVolume = 1f;
|
||||
|
||||
private volatile float _Duration = -1f;
|
||||
private volatile float _Position;
|
||||
private volatile bool _QueryingDuration;
|
||||
|
||||
public CGstreamerSharpAudioStream(string media, bool prescan)
|
||||
{
|
||||
Element convert = ElementFactory.Make("audioconvert", "convert");
|
||||
Element audiosink = ElementFactory.Make("directsoundsink", "audiosink");
|
||||
var audioSinkBin = new Bin("Audiosink");
|
||||
|
||||
if (convert == null || audiosink == null)
|
||||
{
|
||||
CLog.LogError("Could not create pipeline");
|
||||
return;
|
||||
}
|
||||
|
||||
audioSinkBin.Add(convert);
|
||||
audioSinkBin.Add(audiosink);
|
||||
convert.Link(audiosink);
|
||||
Pad pad = convert.GetStaticPad("sink");
|
||||
GhostPad ghostpad = new GhostPad("sink", pad);
|
||||
|
||||
if (pad == null)
|
||||
{
|
||||
CLog.LogError("Could not create pads");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ghostpad.SetActive(true))
|
||||
{
|
||||
CLog.LogError("Could not link pads");
|
||||
return;
|
||||
}
|
||||
if (!audioSinkBin.AddPad(ghostpad))
|
||||
{
|
||||
CLog.LogError("Could not add pad");
|
||||
return;
|
||||
}
|
||||
|
||||
_Element = ElementFactory.Make("playbin", "playbin");
|
||||
if (_Element == null)
|
||||
{
|
||||
CLog.LogError("Could not create playbin");
|
||||
return;
|
||||
}
|
||||
_Element["audio-sink"] = audioSinkBin;
|
||||
_Element["flags"] = 1 << 1;
|
||||
_Element["uri"] = new Uri(media).AbsoluteUri;
|
||||
_Element.SetState(State.Paused);
|
||||
|
||||
Closed = false; //Enable stream
|
||||
|
||||
// Passing CLOCK_TIME_NONE here causes the pipeline to block for a long time so with
|
||||
// prescan enabled the pipeline will wait 500ms for stream to initialize and then continue
|
||||
// if it takes more than 500ms, duration queries will be performed asynchronously
|
||||
if (prescan)
|
||||
{
|
||||
Message msg = _Element.Bus.TimedPopFiltered(0xffffffffffffffff, MessageType.AsyncDone);
|
||||
if (msg.Handle != IntPtr.Zero)
|
||||
_UpdateDuration();
|
||||
}
|
||||
}
|
||||
|
||||
private void _OnMessage(Message msg)
|
||||
{
|
||||
if (msg.Handle == IntPtr.Zero)
|
||||
return;
|
||||
switch (msg.Type)
|
||||
{
|
||||
case MessageType.Eos:
|
||||
if (_Loop)
|
||||
Position = 0;
|
||||
else
|
||||
{
|
||||
Finished = true;
|
||||
Close();
|
||||
}
|
||||
break;
|
||||
case MessageType.Error:
|
||||
GException error;
|
||||
string debug;
|
||||
msg.ParseError(out error, out debug);
|
||||
CLog.LogError("Gstreamer error: message" + error.Message + ", code" + error.Code + " ,debug information" + debug);
|
||||
break;
|
||||
case MessageType.DurationChanged:
|
||||
if (!_QueryingDuration)
|
||||
_UpdateDuration();
|
||||
break;
|
||||
}
|
||||
msg.Unref();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (!Closed)
|
||||
{
|
||||
Closed = true;
|
||||
Finished = true;
|
||||
var t = new System.Threading.Thread(_TerminateStream) {Name = "GSt Terminate"};
|
||||
t.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void _TerminateStream()
|
||||
{
|
||||
if (_Element != null)
|
||||
{
|
||||
_Element.SetState(State.Null);
|
||||
_Element.Dispose();
|
||||
_Element = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Play(bool loop = false)
|
||||
{
|
||||
if (_Element != null)
|
||||
_Element.SetState(State.Playing);
|
||||
_Loop = loop;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_Element != null)
|
||||
_Element.SetState(State.Ready);
|
||||
Position = 0;
|
||||
}
|
||||
|
||||
public void Fade(float targetVolume, float seconds)
|
||||
{
|
||||
_Fading = new CFading(_Volume, targetVolume, seconds);
|
||||
_AfterFadeAction = EStreamAction.Nothing;
|
||||
}
|
||||
|
||||
public void FadeAndPause(float targetVolume, float seconds)
|
||||
{
|
||||
Fade(targetVolume, seconds);
|
||||
_AfterFadeAction = EStreamAction.Pause;
|
||||
}
|
||||
|
||||
public void FadeAndClose(float targetVolume, float seconds)
|
||||
{
|
||||
Fade(targetVolume, seconds);
|
||||
_AfterFadeAction = EStreamAction.Close;
|
||||
}
|
||||
|
||||
public void FadeAndStop(float targetVolume, float seconds)
|
||||
{
|
||||
Fade(targetVolume, seconds);
|
||||
_AfterFadeAction = EStreamAction.Stop;
|
||||
}
|
||||
|
||||
public float Volume
|
||||
{
|
||||
get { return _Volume * 100f; }
|
||||
set
|
||||
{
|
||||
_Volume = value / 100f;
|
||||
if (_Element != null)
|
||||
_Element["volume"] = (_Volume * _MaxVolume);
|
||||
}
|
||||
}
|
||||
|
||||
public float MaxVolume
|
||||
{
|
||||
get { return _MaxVolume * 100f; }
|
||||
set
|
||||
{
|
||||
_MaxVolume = value / 100f;
|
||||
Volume = _Volume;
|
||||
}
|
||||
}
|
||||
|
||||
public float Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_Duration < 0 && !_QueryingDuration)
|
||||
{
|
||||
_QueryingDuration = true; // Set this to avoid race conditions
|
||||
var t = new System.Threading.Thread(_UpdateDuration) {Name = "GSt Update Duration"};
|
||||
t.Start();
|
||||
}
|
||||
return _Duration > 0 ? _Duration : -1;
|
||||
}
|
||||
}
|
||||
|
||||
public float Position
|
||||
{
|
||||
get
|
||||
{
|
||||
long position;
|
||||
if (!_Element.QueryPosition(Format.Time, out position))
|
||||
CLog.LogError("Could not query position");
|
||||
else
|
||||
_Position = (float)(position / (double)Constants.SECOND);
|
||||
return _Position;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_Element != null)
|
||||
_Element.SeekSimple(Format.Time, SeekFlags.Accurate | SeekFlags.Flush, (long)(value * (double)Constants.SECOND));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Paused
|
||||
{
|
||||
get { return _Element == null || _Element.TargetState == State.Paused; }
|
||||
set
|
||||
{
|
||||
if (value && _Element != null)
|
||||
_Element.SetState(State.Paused);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Playing
|
||||
{
|
||||
get { return _Element != null && (_Element.TargetState == State.Playing && !Finished); }
|
||||
set
|
||||
{
|
||||
if (value && _Element != null)
|
||||
_Element.SetState(State.Playing);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
while (_Element != null && _Element.Bus != null && _Element.Bus.HavePending())
|
||||
_OnMessage(_Element.Bus.Pop());
|
||||
|
||||
if (_Fading != null)
|
||||
{
|
||||
bool finished;
|
||||
Volume = _Fading.GetValue(out finished);
|
||||
if (finished)
|
||||
{
|
||||
switch (_AfterFadeAction)
|
||||
{
|
||||
case EStreamAction.Pause:
|
||||
Paused = true;
|
||||
break;
|
||||
case EStreamAction.Stop:
|
||||
Stop();
|
||||
break;
|
||||
case EStreamAction.Close:
|
||||
Close();
|
||||
break;
|
||||
}
|
||||
_Fading = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void _UpdateDuration()
|
||||
{
|
||||
_QueryingDuration = true;
|
||||
long duration = -1;
|
||||
while (duration < 0 && !Closed && !Finished && _Element != null)
|
||||
{
|
||||
if (_Element.QueryDuration(Format.Time, out duration))
|
||||
{
|
||||
_Duration = duration / (float)Constants.SECOND;
|
||||
_QueryingDuration = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using OpenTK.Audio;
|
||||
using Vocaluxe.Base;
|
||||
using Vocaluxe.Lib.Sound.Playback.Decoder;
|
||||
using VocaluxeLib;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Playback.OpenAL
|
||||
{
|
||||
class COpenAlStream : IDisposable
|
||||
{
|
||||
private const int _BufferSize = 2048;
|
||||
private const int _BufferCount = 5;
|
||||
private const long _Bufsize = 50000L;
|
||||
|
||||
private Object _CloseMutex;
|
||||
|
||||
private int[] _Buffers;
|
||||
private int _State;
|
||||
private int _Source;
|
||||
private SFormatInfo _Format;
|
||||
|
||||
private bool _Initialized;
|
||||
private int _ByteCount = 4;
|
||||
private float _Volume = 1f;
|
||||
private float _VolumeMax = 1f;
|
||||
|
||||
private EStreamAction _AfterFadeAction;
|
||||
private CFading _Fading;
|
||||
|
||||
private readonly Stopwatch _Timer = new Stopwatch();
|
||||
|
||||
private Closeproc _Closeproc;
|
||||
private int _StreamID;
|
||||
private IAudioDecoder _Decoder;
|
||||
private float _BytesPerSecond;
|
||||
private bool _NoMoreData;
|
||||
|
||||
private bool _FileOpened;
|
||||
|
||||
private bool _Waiting;
|
||||
private bool _Skip;
|
||||
|
||||
private bool _Loop;
|
||||
private float _Duration;
|
||||
private float _CurrentTime;
|
||||
private float _TimeCode;
|
||||
|
||||
private bool _Paused;
|
||||
|
||||
private CRingBuffer _Data;
|
||||
private float _SetStart;
|
||||
private float _Start;
|
||||
private bool _SetLoop;
|
||||
private bool _SetSkip;
|
||||
private bool _Terminated;
|
||||
|
||||
private readonly Thread _DecoderThread;
|
||||
|
||||
private AutoResetEvent _EventDecode = new AutoResetEvent(false);
|
||||
|
||||
private readonly Object _MutexData = new Object();
|
||||
private readonly Object _MutexSyncSignals = new Object();
|
||||
|
||||
public COpenAlStream()
|
||||
{
|
||||
_Initialized = false;
|
||||
_DecoderThread = new Thread(_Execute) {Name = "OpenAL Decode"};
|
||||
}
|
||||
|
||||
public void Free(Closeproc closeProc, int streamID, Object closeMutex)
|
||||
{
|
||||
_Closeproc = closeProc;
|
||||
_StreamID = streamID;
|
||||
_Terminated = true;
|
||||
_CloseMutex = closeMutex;
|
||||
}
|
||||
|
||||
public float Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_FileOpened)
|
||||
return _Duration;
|
||||
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Finished
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_MutexData)
|
||||
{
|
||||
return _NoMoreData && _Data.BytesNotRead == 0L;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float Volume
|
||||
{
|
||||
get { return _Volume * 100f; }
|
||||
set
|
||||
{
|
||||
lock (_MutexData)
|
||||
{
|
||||
_Volume = value / 100f;
|
||||
if (_Volume < 0f)
|
||||
_Volume = 0f;
|
||||
|
||||
if (_Volume > 1f)
|
||||
_Volume = 1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float VolumeMax
|
||||
{
|
||||
get { return _VolumeMax * 100f; }
|
||||
set
|
||||
{
|
||||
lock (_MutexData)
|
||||
{
|
||||
_VolumeMax = value / 100f;
|
||||
if (_VolumeMax < 0f)
|
||||
_VolumeMax = 0f;
|
||||
|
||||
if (_VolumeMax > 1f)
|
||||
_VolumeMax = 1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float Position
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Finished)
|
||||
_Timer.Stop();
|
||||
//TODO: Why not use Decoder.GetPosition()?
|
||||
return _CurrentTime + _Timer.ElapsedMilliseconds / 1000f;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Paused
|
||||
{
|
||||
get { return _Paused; }
|
||||
set
|
||||
{
|
||||
_Paused = value;
|
||||
lock (_MutexSyncSignals)
|
||||
{
|
||||
if (_Paused)
|
||||
{
|
||||
_Timer.Stop();
|
||||
AL.SourceStop(_Source);
|
||||
}
|
||||
else
|
||||
{
|
||||
_Timer.Start();
|
||||
_EventDecode.Set();
|
||||
AL.SourcePlay(_Source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Fade(float targetVolume, float fadeTime)
|
||||
{
|
||||
_Fading = new CFading(_Volume, targetVolume, fadeTime);
|
||||
_AfterFadeAction = EStreamAction.Nothing;
|
||||
}
|
||||
|
||||
public void FadeAndPause(float targetVolume, float fadeTime)
|
||||
{
|
||||
Fade(targetVolume, fadeTime);
|
||||
_AfterFadeAction = EStreamAction.Pause;
|
||||
}
|
||||
|
||||
public void FadeAndStop(float targetVolume, float fadeTime, Closeproc closeProc, int streamID)
|
||||
{
|
||||
_Closeproc = closeProc;
|
||||
_StreamID = streamID;
|
||||
Fade(targetVolume, fadeTime);
|
||||
_AfterFadeAction = EStreamAction.Stop;
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
Paused = false;
|
||||
_AfterFadeAction = EStreamAction.Nothing;
|
||||
AL.SourcePlay(_Source);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
AL.SourceStop(_Source);
|
||||
Skip(0f);
|
||||
}
|
||||
|
||||
public bool Loop
|
||||
{
|
||||
get { return _SetLoop; }
|
||||
set { _SetLoop = value; }
|
||||
}
|
||||
|
||||
public int Open(string fileName)
|
||||
{
|
||||
if (_FileOpened)
|
||||
return -1;
|
||||
|
||||
if (!File.Exists(fileName))
|
||||
return -1;
|
||||
|
||||
_Decoder = new CAudioDecoderFFmpeg();
|
||||
_Decoder.Init();
|
||||
|
||||
try
|
||||
{
|
||||
_Source = AL.GenSource();
|
||||
_Buffers = new int[_BufferCount];
|
||||
for (int i = 0; i < _BufferCount; i++)
|
||||
_Buffers[i] = AL.GenBuffer();
|
||||
|
||||
_State = 0;
|
||||
//AL.SourceQueueBuffers(_source, _buffers.Length, _buffers);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_Initialized = false;
|
||||
CLog.LogError("Error Init OpenAL Playback");
|
||||
return -1;
|
||||
}
|
||||
|
||||
_Decoder.Open(fileName);
|
||||
_Duration = _Decoder.GetLength();
|
||||
|
||||
_Format = _Decoder.GetFormatInfo();
|
||||
_ByteCount = 2 * _Format.ChannelCount;
|
||||
_BytesPerSecond = _Format.SamplesPerSecond * _ByteCount;
|
||||
_CurrentTime = 0f;
|
||||
_Timer.Reset();
|
||||
|
||||
var stream = new SAudioStreams(0) {Handle = _Buffers[0]};
|
||||
|
||||
if (stream.Handle != 0)
|
||||
{
|
||||
_FileOpened = true;
|
||||
_Data = new CRingBuffer(_Bufsize);
|
||||
_NoMoreData = false;
|
||||
_DecoderThread.Priority = ThreadPriority.Normal;
|
||||
_DecoderThread.Name = Path.GetFileName(fileName);
|
||||
_DecoderThread.Start();
|
||||
|
||||
return stream.Handle;
|
||||
}
|
||||
_Initialized = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public bool Skip(float time)
|
||||
{
|
||||
lock (_MutexSyncSignals)
|
||||
{
|
||||
_SetStart = time;
|
||||
_SetSkip = true;
|
||||
}
|
||||
_EventDecode.Set();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Threading
|
||||
private void _DoSkip()
|
||||
{
|
||||
_Decoder.SetPosition(_Start);
|
||||
_CurrentTime = _Start;
|
||||
_TimeCode = _Start;
|
||||
_Timer.Reset();
|
||||
_Waiting = false;
|
||||
|
||||
lock (_MutexData)
|
||||
{
|
||||
_Data = new CRingBuffer(_Bufsize);
|
||||
_NoMoreData = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void _Execute()
|
||||
{
|
||||
while (!_Terminated)
|
||||
{
|
||||
_Waiting = false;
|
||||
if (_EventDecode.WaitOne(1) || !_Waiting)
|
||||
{
|
||||
if (_Skip)
|
||||
{
|
||||
_DoSkip();
|
||||
_Skip = false;
|
||||
}
|
||||
|
||||
_DoDecode();
|
||||
}
|
||||
if (!_Terminated)
|
||||
{
|
||||
lock (_MutexSyncSignals)
|
||||
{
|
||||
if (_SetSkip)
|
||||
_Skip = true;
|
||||
|
||||
_SetSkip = false;
|
||||
|
||||
_Start = _SetStart;
|
||||
_Loop = _SetLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_DoFree();
|
||||
}
|
||||
|
||||
private void _DoDecode()
|
||||
{
|
||||
if (!_FileOpened)
|
||||
return;
|
||||
|
||||
if (_Paused)
|
||||
return;
|
||||
|
||||
if (_Terminated)
|
||||
return;
|
||||
|
||||
float timecode;
|
||||
byte[] buffer;
|
||||
|
||||
bool doIt = false;
|
||||
lock (_MutexData)
|
||||
{
|
||||
if (_Bufsize - 10000L > _Data.BytesNotRead)
|
||||
doIt = true;
|
||||
}
|
||||
|
||||
if (!doIt)
|
||||
return;
|
||||
|
||||
_Decoder.Decode(out buffer, out timecode);
|
||||
|
||||
if (buffer == null)
|
||||
{
|
||||
if (_Loop)
|
||||
{
|
||||
lock (_MutexSyncSignals)
|
||||
{
|
||||
_CurrentTime = 0f;
|
||||
_Start = 0f;
|
||||
}
|
||||
|
||||
_DoSkip();
|
||||
}
|
||||
else
|
||||
_NoMoreData = true;
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_MutexData)
|
||||
{
|
||||
_Data.Write(buffer);
|
||||
_TimeCode = timecode;
|
||||
}
|
||||
}
|
||||
|
||||
private void _DoFree()
|
||||
{
|
||||
if (_Initialized)
|
||||
{
|
||||
lock (_CloseMutex)
|
||||
{
|
||||
Stop();
|
||||
AL.DeleteSource(_Source);
|
||||
AL.DeleteBuffers(_Buffers);
|
||||
}
|
||||
_Decoder.Close();
|
||||
}
|
||||
|
||||
_Closeproc(_StreamID);
|
||||
}
|
||||
#endregion Threading
|
||||
|
||||
private void _Update()
|
||||
{
|
||||
if (_Fading != null)
|
||||
{
|
||||
bool finished;
|
||||
_Volume = _Fading.GetValue(out finished);
|
||||
if (finished)
|
||||
{
|
||||
switch (_AfterFadeAction)
|
||||
{
|
||||
case EStreamAction.Close:
|
||||
case EStreamAction.Stop:
|
||||
_Terminated = true;
|
||||
break;
|
||||
case EStreamAction.Pause:
|
||||
Paused = true;
|
||||
break;
|
||||
}
|
||||
_Fading = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UploadData()
|
||||
{
|
||||
_Update();
|
||||
|
||||
if (_Paused)
|
||||
return;
|
||||
|
||||
int queuedCount;
|
||||
bool doit = true;
|
||||
AL.GetSource(_Source, ALGetSourcei.BuffersQueued, out queuedCount);
|
||||
|
||||
int processedCount = _BufferCount;
|
||||
if (queuedCount > 0)
|
||||
{
|
||||
AL.GetSource(_Source, ALGetSourcei.BuffersProcessed, out processedCount);
|
||||
doit = false;
|
||||
Console.WriteLine("Buffers Processed on Stream " + _Source + " = " + processedCount);
|
||||
if (processedCount < 1)
|
||||
return;
|
||||
}
|
||||
|
||||
var buf = new byte[_BufferSize];
|
||||
|
||||
lock (_MutexData)
|
||||
{
|
||||
while (processedCount > 0)
|
||||
{
|
||||
if (_Data.BytesNotRead >= buf.Length)
|
||||
{
|
||||
_Data.Read(buf);
|
||||
|
||||
|
||||
var 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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int buffer;
|
||||
if (!doit)
|
||||
buffer = AL.SourceUnqueueBuffer(_Source);
|
||||
else
|
||||
{
|
||||
buffer = _Buffers[queuedCount];
|
||||
queuedCount++;
|
||||
}
|
||||
|
||||
if (buffer != 0)
|
||||
{
|
||||
ALFormat alFormat = (_Format.ChannelCount == 2) ? ALFormat.Stereo16 : ALFormat.Mono16;
|
||||
AL.BufferData(buffer, alFormat, buf, buf.Length, _Format.SamplesPerSecond);
|
||||
Console.WriteLine("Write to Buffer: " + buffer);
|
||||
AL.SourceQueueBuffer(_Source, buffer);
|
||||
}
|
||||
}
|
||||
processedCount--;
|
||||
}
|
||||
AL.GetSource(_Source, ALGetSourcei.SourceState, out _State);
|
||||
if ((ALSourceState)_State != ALSourceState.Playing)
|
||||
AL.SourcePlay(_Source);
|
||||
}
|
||||
|
||||
_CurrentTime = _TimeCode - _Data.BytesNotRead / _BytesPerSecond - 0.1f;
|
||||
_Timer.Reset();
|
||||
_Timer.Start();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_EventDecode.Close();
|
||||
_EventDecode = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,14 +17,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Vocaluxe.Base;
|
||||
using Vocaluxe.Lib.Sound.Playback.Decoder;
|
||||
using Vocaluxe.Lib.Sound.PortAudio;
|
||||
using VocaluxeLib;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
{
|
||||
@@ -337,610 +330,4 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CPortAudioStream : IDisposable
|
||||
{
|
||||
private const long _Bufsize = 1000000L;
|
||||
private const long _Beginrefill = 800000L;
|
||||
|
||||
private readonly CSyncTimer _SyncTimer;
|
||||
private static bool _Initialized;
|
||||
private static int _NumStreams;
|
||||
private static readonly Object _Mutex = new object();
|
||||
private int _ByteCount = 4;
|
||||
private float _Volume = 1f;
|
||||
private float _VolumeMax = 1f;
|
||||
|
||||
private EStreamAction _AfterFadeAction;
|
||||
private CFading _Fading;
|
||||
|
||||
private static CPortAudio.SPaHostApiInfo _ApiInfo;
|
||||
private static CPortAudio.SPaDeviceInfo _OutputDeviceInfo;
|
||||
private IntPtr _Ptr = new IntPtr(0);
|
||||
|
||||
private Closeproc _Closeproc;
|
||||
private CPortAudio.PaStreamCallbackDelegate _PaStreamCallback;
|
||||
private int _StreamID;
|
||||
private IAudioDecoder _Decoder;
|
||||
private float _BytesPerSecond;
|
||||
private bool _NoMoreData;
|
||||
|
||||
private bool _FileOpened;
|
||||
|
||||
private bool _Waiting;
|
||||
private bool _Skip;
|
||||
|
||||
private bool _Loop;
|
||||
private float _Duration;
|
||||
private float _CurrentTime;
|
||||
private float _TimeCode;
|
||||
|
||||
private bool _Paused;
|
||||
|
||||
private CRingBuffer _Data;
|
||||
private float _SetStart;
|
||||
private float _Start;
|
||||
private bool _SetLoop;
|
||||
private bool _SetSkip;
|
||||
private bool _Terminated;
|
||||
|
||||
private readonly Thread _DecoderThread;
|
||||
|
||||
private AutoResetEvent _EventDecode = new AutoResetEvent(false);
|
||||
|
||||
private readonly Object _LockData = new Object();
|
||||
private readonly Object _LockSyncSignals = new Object();
|
||||
|
||||
public CPortAudioStream()
|
||||
{
|
||||
_SyncTimer = new CSyncTimer(0f, 1f, 0.02f);
|
||||
_DecoderThread = new Thread(_Execute) {Name = "PortAudio Decode"};
|
||||
}
|
||||
|
||||
~CPortAudioStream()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Free(Closeproc closeProc, int streamID)
|
||||
{
|
||||
_Closeproc = closeProc;
|
||||
_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();
|
||||
//TODO: Why not use Decoder.GetPosition()?
|
||||
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 = new CFading(_Volume, targetVolume, fadeTime);
|
||||
_AfterFadeAction = EStreamAction.Nothing;
|
||||
}
|
||||
|
||||
public void FadeAndPause(float targetVolume, float fadeTime)
|
||||
{
|
||||
Fade(targetVolume, fadeTime);
|
||||
}
|
||||
|
||||
public void FadeAndClose(float targetVolume, float fadeTime, Closeproc closeProc, int streamID)
|
||||
{
|
||||
_Closeproc = closeProc;
|
||||
_StreamID = streamID;
|
||||
Fade(targetVolume, fadeTime);
|
||||
_AfterFadeAction = EStreamAction.Close;
|
||||
}
|
||||
|
||||
public void FadeAndStop(float targetVolume, float fadeTime, Closeproc closeProc, int streamID)
|
||||
{
|
||||
_Closeproc = closeProc;
|
||||
_StreamID = streamID;
|
||||
Fade(targetVolume, fadeTime);
|
||||
_AfterFadeAction = EStreamAction.Stop;
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
Paused = false;
|
||||
_AfterFadeAction = EStreamAction.Nothing;
|
||||
lock (_Mutex)
|
||||
{
|
||||
_ErrorCheck("StartStream", CPortAudio.Pa_StartStream(_Ptr));
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
lock (_Mutex)
|
||||
{
|
||||
_ErrorCheck("StopStream (playback)", CPortAudio.Pa_StopStream(_Ptr));
|
||||
}
|
||||
Skip(0f);
|
||||
}
|
||||
|
||||
public bool Loop
|
||||
{
|
||||
get { return _SetLoop; }
|
||||
set { _SetLoop = value; }
|
||||
}
|
||||
|
||||
public int Open(string fileName)
|
||||
{
|
||||
if (_FileOpened)
|
||||
return -1;
|
||||
|
||||
if (!File.Exists(fileName))
|
||||
return -1;
|
||||
|
||||
if (_FileOpened)
|
||||
return -1;
|
||||
|
||||
_Decoder = new CAudioDecoderFFmpeg();
|
||||
_Decoder.Init();
|
||||
|
||||
try
|
||||
{
|
||||
lock (_Mutex)
|
||||
{
|
||||
if (!_Initialized)
|
||||
{
|
||||
if (_ErrorCheck("Initialize", CPortAudio.Pa_Initialize()))
|
||||
return -1;
|
||||
_Initialized = true;
|
||||
|
||||
int hostApi = _ApiSelect();
|
||||
_ApiInfo = CPortAudio.PaGetHostApiInfo(hostApi);
|
||||
_OutputDeviceInfo = CPortAudio.PaGetDeviceInfo(_ApiInfo.DefaultOutputDevice);
|
||||
if (_OutputDeviceInfo.DefaultLowOutputLatency < 0.1)
|
||||
_OutputDeviceInfo.DefaultLowOutputLatency = 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
_PaStreamCallback = _ProcessNewData;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_Initialized = false;
|
||||
CLog.LogError("Error Init PortAudio Playback");
|
||||
return -1;
|
||||
}
|
||||
|
||||
_Decoder.Open(fileName);
|
||||
_Duration = _Decoder.GetLength();
|
||||
|
||||
SFormatInfo format = _Decoder.GetFormatInfo();
|
||||
if (format.SamplesPerSecond == 0)
|
||||
return -1;
|
||||
|
||||
_ByteCount = 2 * format.ChannelCount;
|
||||
_BytesPerSecond = format.SamplesPerSecond * _ByteCount;
|
||||
_CurrentTime = 0f;
|
||||
_SyncTimer.Time = _CurrentTime;
|
||||
|
||||
var stream = new SAudioStreams(0);
|
||||
|
||||
var data = new IntPtr(0);
|
||||
|
||||
var outputParams = new CPortAudio.SPaStreamParameters
|
||||
{
|
||||
ChannelCount = format.ChannelCount,
|
||||
Device = _ApiInfo.DefaultOutputDevice,
|
||||
SampleFormat = CPortAudio.EPaSampleFormat.PaInt16,
|
||||
SuggestedLatency = _OutputDeviceInfo.DefaultLowOutputLatency
|
||||
};
|
||||
|
||||
var bufsize = (uint)CConfig.AudioBufferSize;
|
||||
lock (_Mutex)
|
||||
{
|
||||
_ErrorCheck("OpenDefaultStream (playback)", CPortAudio.Pa_OpenStream(
|
||||
out _Ptr,
|
||||
IntPtr.Zero,
|
||||
ref outputParams,
|
||||
format.SamplesPerSecond,
|
||||
bufsize,
|
||||
CPortAudio.EPaStreamFlags.PaNoFlag,
|
||||
_PaStreamCallback,
|
||||
data));
|
||||
}
|
||||
|
||||
stream.Handle = _Ptr.ToInt32();
|
||||
|
||||
if (stream.Handle != 0)
|
||||
{
|
||||
_NumStreams++;
|
||||
_Paused = true;
|
||||
_Waiting = true;
|
||||
_FileOpened = true;
|
||||
_Data = new CRingBuffer(_Bufsize);
|
||||
_NoMoreData = false;
|
||||
_DecoderThread.Priority = ThreadPriority.Normal;
|
||||
_DecoderThread.Name = Path.GetFileName(fileName);
|
||||
_DecoderThread.Start();
|
||||
|
||||
return stream.Handle;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Skip(float time)
|
||||
{
|
||||
lock (_LockSyncSignals)
|
||||
{
|
||||
_SetStart = time;
|
||||
_SetSkip = true;
|
||||
_Waiting = true;
|
||||
}
|
||||
}
|
||||
|
||||
#region Threading
|
||||
private void _DoSkip()
|
||||
{
|
||||
lock (_LockData)
|
||||
{
|
||||
_Decoder.SetPosition(_Start);
|
||||
_CurrentTime = _Start;
|
||||
_TimeCode = _Start;
|
||||
_Data = new CRingBuffer(_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();
|
||||
_NumStreams--;
|
||||
if (_NumStreams == 0)
|
||||
{
|
||||
lock (_Mutex)
|
||||
{
|
||||
CPortAudio.Pa_Terminate();
|
||||
_Initialized = false;
|
||||
}
|
||||
}
|
||||
_Decoder.Close();
|
||||
}
|
||||
|
||||
_Closeproc(_StreamID);
|
||||
}
|
||||
#endregion Threading
|
||||
|
||||
#region Callbacks
|
||||
private CPortAudio.EPaStreamCallbackResult _ProcessNewData(
|
||||
IntPtr input,
|
||||
IntPtr output,
|
||||
uint frameCount,
|
||||
ref CPortAudio.SPaStreamCallbackTimeInfo timeInfo,
|
||||
CPortAudio.EPaStreamCallbackFlags statusFlags,
|
||||
IntPtr userData)
|
||||
{
|
||||
var buf = new byte[frameCount * _ByteCount];
|
||||
|
||||
if (_Paused)
|
||||
{
|
||||
try
|
||||
{
|
||||
Marshal.Copy(buf, 0, output, (int)frameCount * _ByteCount);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.ToString());
|
||||
}
|
||||
return CPortAudio.EPaStreamCallbackResult.PaContinue;
|
||||
}
|
||||
|
||||
lock (_LockData)
|
||||
{
|
||||
if (_NoMoreData || _Data.BytesNotRead >= buf.Length)
|
||||
{
|
||||
_Data.Read(buf);
|
||||
|
||||
var 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 CPortAudio.EPaStreamCallbackResult.PaContinue;
|
||||
}
|
||||
#endregion Callbacks
|
||||
|
||||
private bool _ErrorCheck(String action, CPortAudio.EPaError errorCode)
|
||||
{
|
||||
if (errorCode != CPortAudio.EPaError.PaNoError)
|
||||
{
|
||||
if (errorCode == CPortAudio.EPaError.PaStreamIsNotStopped)
|
||||
return false;
|
||||
|
||||
CLog.LogError(action + " error (playback): " + CPortAudio.PaGetErrorText(errorCode));
|
||||
if (errorCode == CPortAudio.EPaError.PaUnanticipatedHostError)
|
||||
{
|
||||
CPortAudio.SPaHostErrorInfo errorInfo = CPortAudio.PaGetLastHostErrorInfo();
|
||||
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 = CPortAudio.Pa_GetDefaultHostApi();
|
||||
int apiCount = CPortAudio.Pa_GetHostApiCount();
|
||||
for (int i = 0; i < apiCount; i++)
|
||||
{
|
||||
CPortAudio.SPaHostApiInfo apiInfo = CPortAudio.PaGetHostApiInfo(i);
|
||||
if ((apiInfo.Type == CPortAudio.EPaHostApiTypeId.PaDirectSound)
|
||||
|| (apiInfo.Type == CPortAudio.EPaHostApiTypeId.PaALSA))
|
||||
selectedHostApi = i;
|
||||
}
|
||||
return selectedHostApi;
|
||||
}
|
||||
|
||||
private void _Update()
|
||||
{
|
||||
if (_Fading != null)
|
||||
{
|
||||
bool finished;
|
||||
_Volume = _Fading.GetValue(out finished);
|
||||
if (finished)
|
||||
{
|
||||
switch (_AfterFadeAction)
|
||||
{
|
||||
case EStreamAction.Close:
|
||||
_Terminated = true;
|
||||
break;
|
||||
case EStreamAction.Stop:
|
||||
Stop();
|
||||
break;
|
||||
case EStreamAction.Pause:
|
||||
Paused = true;
|
||||
break;
|
||||
}
|
||||
_Fading = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_EventDecode != null)
|
||||
{
|
||||
_EventDecode.Close();
|
||||
_EventDecode = null;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Vocaluxe.Base;
|
||||
using Vocaluxe.Lib.Sound.Playback.Decoder;
|
||||
using Vocaluxe.Lib.Sound.PortAudio;
|
||||
using VocaluxeLib;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
{
|
||||
class CPortAudioStream : IDisposable
|
||||
{
|
||||
private const long _Bufsize = 1000000L;
|
||||
private const long _Beginrefill = 800000L;
|
||||
|
||||
private readonly CSyncTimer _SyncTimer;
|
||||
private static bool _Initialized;
|
||||
private static int _NumStreams;
|
||||
private static readonly Object _Mutex = new object();
|
||||
private int _ByteCount = 4;
|
||||
private float _Volume = 1f;
|
||||
private float _VolumeMax = 1f;
|
||||
|
||||
private EStreamAction _AfterFadeAction;
|
||||
private CFading _Fading;
|
||||
|
||||
private static CPortAudio.SPaHostApiInfo _ApiInfo;
|
||||
private static CPortAudio.SPaDeviceInfo _OutputDeviceInfo;
|
||||
private IntPtr _Ptr = new IntPtr(0);
|
||||
|
||||
private Closeproc _Closeproc;
|
||||
private CPortAudio.PaStreamCallbackDelegate _PaStreamCallback;
|
||||
private int _StreamID;
|
||||
private IAudioDecoder _Decoder;
|
||||
private float _BytesPerSecond;
|
||||
private bool _NoMoreData;
|
||||
|
||||
private bool _FileOpened;
|
||||
|
||||
private bool _Waiting;
|
||||
private bool _Skip;
|
||||
|
||||
private bool _Loop;
|
||||
private float _Duration;
|
||||
private float _CurrentTime;
|
||||
private float _TimeCode;
|
||||
|
||||
private bool _Paused;
|
||||
|
||||
private CRingBuffer _Data;
|
||||
private float _SetStart;
|
||||
private float _Start;
|
||||
private bool _SetLoop;
|
||||
private bool _SetSkip;
|
||||
private bool _Terminated;
|
||||
|
||||
private readonly Thread _DecoderThread;
|
||||
|
||||
private AutoResetEvent _EventDecode = new AutoResetEvent(false);
|
||||
|
||||
private readonly Object _LockData = new Object();
|
||||
private readonly Object _LockSyncSignals = new Object();
|
||||
|
||||
public CPortAudioStream()
|
||||
{
|
||||
_SyncTimer = new CSyncTimer(0f, 1f, 0.02f);
|
||||
_DecoderThread = new Thread(_Execute) {Name = "PortAudio Decode"};
|
||||
}
|
||||
|
||||
~CPortAudioStream()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Free(Closeproc closeProc, int streamID)
|
||||
{
|
||||
_Closeproc = closeProc;
|
||||
_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();
|
||||
//TODO: Why not use Decoder.GetPosition()?
|
||||
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 = new CFading(_Volume, targetVolume, fadeTime);
|
||||
_AfterFadeAction = EStreamAction.Nothing;
|
||||
}
|
||||
|
||||
public void FadeAndPause(float targetVolume, float fadeTime)
|
||||
{
|
||||
Fade(targetVolume, fadeTime);
|
||||
}
|
||||
|
||||
public void FadeAndClose(float targetVolume, float fadeTime, Closeproc closeProc, int streamID)
|
||||
{
|
||||
_Closeproc = closeProc;
|
||||
_StreamID = streamID;
|
||||
Fade(targetVolume, fadeTime);
|
||||
_AfterFadeAction = EStreamAction.Close;
|
||||
}
|
||||
|
||||
public void FadeAndStop(float targetVolume, float fadeTime, Closeproc closeProc, int streamID)
|
||||
{
|
||||
_Closeproc = closeProc;
|
||||
_StreamID = streamID;
|
||||
Fade(targetVolume, fadeTime);
|
||||
_AfterFadeAction = EStreamAction.Stop;
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
Paused = false;
|
||||
_AfterFadeAction = EStreamAction.Nothing;
|
||||
lock (_Mutex)
|
||||
{
|
||||
_ErrorCheck("StartStream", CPortAudio.Pa_StartStream(_Ptr));
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
lock (_Mutex)
|
||||
{
|
||||
_ErrorCheck("StopStream (playback)", CPortAudio.Pa_StopStream(_Ptr));
|
||||
}
|
||||
Skip(0f);
|
||||
}
|
||||
|
||||
public bool Loop
|
||||
{
|
||||
get { return _SetLoop; }
|
||||
set { _SetLoop = value; }
|
||||
}
|
||||
|
||||
public int Open(string fileName)
|
||||
{
|
||||
if (_FileOpened)
|
||||
return -1;
|
||||
|
||||
if (!File.Exists(fileName))
|
||||
return -1;
|
||||
|
||||
if (_FileOpened)
|
||||
return -1;
|
||||
|
||||
_Decoder = new CAudioDecoderFFmpeg();
|
||||
_Decoder.Init();
|
||||
|
||||
try
|
||||
{
|
||||
lock (_Mutex)
|
||||
{
|
||||
if (!_Initialized)
|
||||
{
|
||||
if (_ErrorCheck("Initialize", CPortAudio.Pa_Initialize()))
|
||||
return -1;
|
||||
_Initialized = true;
|
||||
|
||||
int hostApi = _ApiSelect();
|
||||
_ApiInfo = CPortAudio.PaGetHostApiInfo(hostApi);
|
||||
_OutputDeviceInfo = CPortAudio.PaGetDeviceInfo(_ApiInfo.DefaultOutputDevice);
|
||||
if (_OutputDeviceInfo.DefaultLowOutputLatency < 0.1)
|
||||
_OutputDeviceInfo.DefaultLowOutputLatency = 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
_PaStreamCallback = _ProcessNewData;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_Initialized = false;
|
||||
CLog.LogError("Error Init PortAudio Playback");
|
||||
return -1;
|
||||
}
|
||||
|
||||
_Decoder.Open(fileName);
|
||||
_Duration = _Decoder.GetLength();
|
||||
|
||||
SFormatInfo format = _Decoder.GetFormatInfo();
|
||||
if (format.SamplesPerSecond == 0)
|
||||
return -1;
|
||||
|
||||
_ByteCount = 2 * format.ChannelCount;
|
||||
_BytesPerSecond = format.SamplesPerSecond * _ByteCount;
|
||||
_CurrentTime = 0f;
|
||||
_SyncTimer.Time = _CurrentTime;
|
||||
|
||||
var stream = new SAudioStreams(0);
|
||||
|
||||
var data = new IntPtr(0);
|
||||
|
||||
var outputParams = new CPortAudio.SPaStreamParameters
|
||||
{
|
||||
ChannelCount = format.ChannelCount,
|
||||
Device = _ApiInfo.DefaultOutputDevice,
|
||||
SampleFormat = CPortAudio.EPaSampleFormat.PaInt16,
|
||||
SuggestedLatency = _OutputDeviceInfo.DefaultLowOutputLatency
|
||||
};
|
||||
|
||||
var bufsize = (uint)CConfig.AudioBufferSize;
|
||||
lock (_Mutex)
|
||||
{
|
||||
_ErrorCheck("OpenDefaultStream (playback)", CPortAudio.Pa_OpenStream(
|
||||
out _Ptr,
|
||||
IntPtr.Zero,
|
||||
ref outputParams,
|
||||
format.SamplesPerSecond,
|
||||
bufsize,
|
||||
CPortAudio.EPaStreamFlags.PaNoFlag,
|
||||
_PaStreamCallback,
|
||||
data));
|
||||
}
|
||||
|
||||
stream.Handle = _Ptr.ToInt32();
|
||||
|
||||
if (stream.Handle != 0)
|
||||
{
|
||||
_NumStreams++;
|
||||
_Paused = true;
|
||||
_Waiting = true;
|
||||
_FileOpened = true;
|
||||
_Data = new CRingBuffer(_Bufsize);
|
||||
_NoMoreData = false;
|
||||
_DecoderThread.Priority = ThreadPriority.Normal;
|
||||
_DecoderThread.Name = Path.GetFileName(fileName);
|
||||
_DecoderThread.Start();
|
||||
|
||||
return stream.Handle;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Skip(float time)
|
||||
{
|
||||
lock (_LockSyncSignals)
|
||||
{
|
||||
_SetStart = time;
|
||||
_SetSkip = true;
|
||||
_Waiting = true;
|
||||
}
|
||||
}
|
||||
|
||||
#region Threading
|
||||
private void _DoSkip()
|
||||
{
|
||||
lock (_LockData)
|
||||
{
|
||||
_Decoder.SetPosition(_Start);
|
||||
_CurrentTime = _Start;
|
||||
_TimeCode = _Start;
|
||||
_Data = new CRingBuffer(_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();
|
||||
_NumStreams--;
|
||||
if (_NumStreams == 0)
|
||||
{
|
||||
lock (_Mutex)
|
||||
{
|
||||
CPortAudio.Pa_Terminate();
|
||||
_Initialized = false;
|
||||
}
|
||||
}
|
||||
_Decoder.Close();
|
||||
}
|
||||
|
||||
_Closeproc(_StreamID);
|
||||
}
|
||||
#endregion Threading
|
||||
|
||||
#region Callbacks
|
||||
private CPortAudio.EPaStreamCallbackResult _ProcessNewData(
|
||||
IntPtr input,
|
||||
IntPtr output,
|
||||
uint frameCount,
|
||||
ref CPortAudio.SPaStreamCallbackTimeInfo timeInfo,
|
||||
CPortAudio.EPaStreamCallbackFlags statusFlags,
|
||||
IntPtr userData)
|
||||
{
|
||||
var buf = new byte[frameCount * _ByteCount];
|
||||
|
||||
if (_Paused)
|
||||
{
|
||||
try
|
||||
{
|
||||
Marshal.Copy(buf, 0, output, (int)frameCount * _ByteCount);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.ToString());
|
||||
}
|
||||
return CPortAudio.EPaStreamCallbackResult.PaContinue;
|
||||
}
|
||||
|
||||
lock (_LockData)
|
||||
{
|
||||
if (_NoMoreData || _Data.BytesNotRead >= buf.Length)
|
||||
{
|
||||
_Data.Read(buf);
|
||||
|
||||
var 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 CPortAudio.EPaStreamCallbackResult.PaContinue;
|
||||
}
|
||||
#endregion Callbacks
|
||||
|
||||
private bool _ErrorCheck(String action, CPortAudio.EPaError errorCode)
|
||||
{
|
||||
if (errorCode != CPortAudio.EPaError.PaNoError)
|
||||
{
|
||||
if (errorCode == CPortAudio.EPaError.PaStreamIsNotStopped)
|
||||
return false;
|
||||
|
||||
CLog.LogError(action + " error (playback): " + CPortAudio.PaGetErrorText(errorCode));
|
||||
if (errorCode == CPortAudio.EPaError.PaUnanticipatedHostError)
|
||||
{
|
||||
CPortAudio.SPaHostErrorInfo errorInfo = CPortAudio.PaGetLastHostErrorInfo();
|
||||
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 = CPortAudio.Pa_GetDefaultHostApi();
|
||||
int apiCount = CPortAudio.Pa_GetHostApiCount();
|
||||
for (int i = 0; i < apiCount; i++)
|
||||
{
|
||||
CPortAudio.SPaHostApiInfo apiInfo = CPortAudio.PaGetHostApiInfo(i);
|
||||
if ((apiInfo.Type == CPortAudio.EPaHostApiTypeId.PaDirectSound)
|
||||
|| (apiInfo.Type == CPortAudio.EPaHostApiTypeId.PaALSA))
|
||||
selectedHostApi = i;
|
||||
}
|
||||
return selectedHostApi;
|
||||
}
|
||||
|
||||
private void _Update()
|
||||
{
|
||||
if (_Fading != null)
|
||||
{
|
||||
bool finished;
|
||||
_Volume = _Fading.GetValue(out finished);
|
||||
if (finished)
|
||||
{
|
||||
switch (_AfterFadeAction)
|
||||
{
|
||||
case EStreamAction.Close:
|
||||
_Terminated = true;
|
||||
break;
|
||||
case EStreamAction.Stop:
|
||||
Stop();
|
||||
break;
|
||||
case EStreamAction.Pause:
|
||||
Paused = true;
|
||||
break;
|
||||
}
|
||||
_Fading = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_EventDecode != null)
|
||||
{
|
||||
_EventDecode.Close();
|
||||
_EventDecode = null;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,8 @@
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using SlimDX.DirectSound;
|
||||
using SlimDX.Multimedia;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Vocaluxe.Base;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.DirectSound
|
||||
@@ -53,7 +51,7 @@ namespace Vocaluxe.Lib.Sound.Record.DirectSound
|
||||
{
|
||||
using (var ds = new DirectSoundCapture(dev.DriverGuid))
|
||||
{
|
||||
var device = new CRecordDevice(id,dev.DriverGuid.ToString(), dev.Description, ds.Capabilities.Channels);
|
||||
var device = new CRecordDevice(id, dev.DriverGuid.ToString(), dev.Description, ds.Capabilities.Channels);
|
||||
|
||||
_Devices.Add(device);
|
||||
|
||||
@@ -229,184 +227,5 @@ namespace Vocaluxe.Lib.Sound.Record.DirectSound
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CSampleDataEventArgs : EventArgs
|
||||
{
|
||||
public CSampleDataEventArgs(byte[] data, Guid guid)
|
||||
{
|
||||
Data = data;
|
||||
Guid = guid;
|
||||
}
|
||||
|
||||
public byte[] Data { get; private set; }
|
||||
public Guid Guid { get; private set; }
|
||||
}
|
||||
|
||||
private class CSoundCardSource : IDisposable
|
||||
{
|
||||
private volatile bool _Running;
|
||||
private readonly int _BufferSize;
|
||||
private CaptureBuffer _CaptureBuffer;
|
||||
private CaptureBufferDescription _BufferDescription;
|
||||
private DirectSoundCapture _CaptureDevice;
|
||||
private readonly WaveFormat _WaveFormat;
|
||||
private Thread _CaptureThread;
|
||||
private List<NotificationPosition> _Notifications;
|
||||
private int _BufferPortionCount;
|
||||
private int _BufferPortionSize;
|
||||
private WaitHandle[] _WaitHandles;
|
||||
private double _SampleRate;
|
||||
private readonly Guid _Guid;
|
||||
private readonly short _Channels;
|
||||
|
||||
public CSoundCardSource(Guid guid, short channels)
|
||||
{
|
||||
_Guid = guid;
|
||||
_Channels = channels;
|
||||
_WaveFormat = new WaveFormat();
|
||||
SampleRateKhz = 44.1;
|
||||
_BufferSize = 2048;
|
||||
}
|
||||
|
||||
public event EventHandler<CSampleDataEventArgs> SampleDataReady = delegate { };
|
||||
|
||||
public double SampleRateKhz
|
||||
{
|
||||
get { return _SampleRate; }
|
||||
|
||||
set
|
||||
{
|
||||
_SampleRate = value;
|
||||
|
||||
if (_Running)
|
||||
Restart();
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_Running)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
if (_CaptureDevice == null)
|
||||
_CaptureDevice = new DirectSoundCapture(_Guid);
|
||||
|
||||
_WaveFormat.FormatTag = WaveFormatTag.Pcm; // Change to WaveFormatTag.IeeeFloat for float
|
||||
_WaveFormat.BitsPerSample = 16; // Set this to 32 for float
|
||||
_WaveFormat.BlockAlignment = (short)(_Channels * (_WaveFormat.BitsPerSample / 8));
|
||||
_WaveFormat.Channels = _Channels;
|
||||
_WaveFormat.SamplesPerSecond = (int)(SampleRateKhz * 1000D);
|
||||
_WaveFormat.AverageBytesPerSecond =
|
||||
_WaveFormat.SamplesPerSecond *
|
||||
_WaveFormat.BlockAlignment;
|
||||
|
||||
_BufferPortionCount = 2;
|
||||
|
||||
_BufferDescription.BufferBytes = _BufferSize * sizeof(short) * _BufferPortionCount * _Channels;
|
||||
_BufferDescription.Format = _WaveFormat;
|
||||
_BufferDescription.WaveMapped = false;
|
||||
|
||||
_CaptureBuffer = new CaptureBuffer(_CaptureDevice, _BufferDescription);
|
||||
|
||||
_BufferPortionSize = _CaptureBuffer.SizeInBytes / _BufferPortionCount;
|
||||
_Notifications = new List<NotificationPosition>();
|
||||
|
||||
for (int i = 0; i < _BufferPortionCount; i++)
|
||||
{
|
||||
var notification = new NotificationPosition {Offset = _BufferPortionCount - 1 + (_BufferPortionSize * i), Event = new AutoResetEvent(false)};
|
||||
_Notifications.Add(notification);
|
||||
}
|
||||
|
||||
_CaptureBuffer.SetNotificationPositions(_Notifications.ToArray());
|
||||
_WaitHandles = new WaitHandle[_Notifications.Count];
|
||||
|
||||
for (int i = 0; i < _Notifications.Count; i++)
|
||||
_WaitHandles[i] = _Notifications[i].Event;
|
||||
|
||||
_CaptureThread = new Thread(_DoCapture) {Name = "DirectSoundCapture", IsBackground = true};
|
||||
|
||||
_Running = true;
|
||||
_CaptureThread.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_Running = false;
|
||||
|
||||
if (_CaptureThread != null)
|
||||
{
|
||||
_CaptureThread.Join();
|
||||
_CaptureThread = null;
|
||||
}
|
||||
|
||||
if (_CaptureBuffer != null)
|
||||
{
|
||||
_CaptureBuffer.Dispose();
|
||||
_CaptureBuffer = null;
|
||||
}
|
||||
|
||||
if (_Notifications != null)
|
||||
{
|
||||
foreach (NotificationPosition notification in _Notifications)
|
||||
notification.Event.Close();
|
||||
|
||||
_Notifications.Clear();
|
||||
_Notifications = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
{
|
||||
Stop();
|
||||
Start();
|
||||
}
|
||||
|
||||
private void _DoCapture()
|
||||
{
|
||||
int bufferPortionSamples = _BufferPortionSize / sizeof(byte);
|
||||
|
||||
// Buffer type must match this.waveFormat.FormatTag and this.waveFormat.BitsPerSample
|
||||
var bufferPortion = new byte[bufferPortionSamples];
|
||||
|
||||
_CaptureBuffer.Start(true);
|
||||
|
||||
while (_Running)
|
||||
{
|
||||
int bufferPortionIndex = WaitHandle.WaitAny(_WaitHandles);
|
||||
|
||||
_CaptureBuffer.Read(
|
||||
bufferPortion,
|
||||
0,
|
||||
bufferPortionSamples,
|
||||
_BufferPortionSize * Math.Abs((bufferPortionIndex - 1) % _BufferPortionCount));
|
||||
|
||||
SampleDataReady(this, new CSampleDataEventArgs(bufferPortion, _Guid));
|
||||
}
|
||||
|
||||
_CaptureBuffer.Stop();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
protected void Dispose(bool disposing)
|
||||
// ReSharper restore InconsistentNaming
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Stop();
|
||||
|
||||
if (_CaptureDevice != null)
|
||||
{
|
||||
_CaptureDevice.Dispose();
|
||||
_CaptureDevice = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.DirectSound
|
||||
{
|
||||
public class CSampleDataEventArgs : EventArgs
|
||||
{
|
||||
public readonly byte[] Data;
|
||||
public readonly Guid Guid;
|
||||
|
||||
public CSampleDataEventArgs(byte[] data, Guid guid)
|
||||
{
|
||||
Data = data;
|
||||
Guid = guid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using SlimDX.DirectSound;
|
||||
using SlimDX.Multimedia;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.DirectSound
|
||||
{
|
||||
public class CSoundCardSource : IDisposable
|
||||
{
|
||||
private volatile bool _Running;
|
||||
private readonly int _BufferSize;
|
||||
private CaptureBuffer _CaptureBuffer;
|
||||
private CaptureBufferDescription _BufferDescription;
|
||||
private DirectSoundCapture _CaptureDevice;
|
||||
private readonly WaveFormat _WaveFormat;
|
||||
private Thread _CaptureThread;
|
||||
private List<NotificationPosition> _Notifications;
|
||||
private int _BufferPortionCount;
|
||||
private int _BufferPortionSize;
|
||||
private WaitHandle[] _WaitHandles;
|
||||
private double _SampleRate;
|
||||
private readonly Guid _Guid;
|
||||
private readonly short _Channels;
|
||||
|
||||
public CSoundCardSource(Guid guid, short channels)
|
||||
{
|
||||
_Guid = guid;
|
||||
_Channels = channels;
|
||||
_WaveFormat = new WaveFormat();
|
||||
SampleRateKhz = 44.1;
|
||||
_BufferSize = 2048;
|
||||
}
|
||||
|
||||
public event EventHandler<CSampleDataEventArgs> SampleDataReady = delegate { };
|
||||
|
||||
public double SampleRateKhz
|
||||
{
|
||||
get { return _SampleRate; }
|
||||
|
||||
set
|
||||
{
|
||||
_SampleRate = value;
|
||||
|
||||
if (_Running)
|
||||
Restart();
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_Running)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
if (_CaptureDevice == null)
|
||||
_CaptureDevice = new DirectSoundCapture(_Guid);
|
||||
|
||||
_WaveFormat.FormatTag = WaveFormatTag.Pcm; // Change to WaveFormatTag.IeeeFloat for float
|
||||
_WaveFormat.BitsPerSample = 16; // Set this to 32 for float
|
||||
_WaveFormat.BlockAlignment = (short)(_Channels * (_WaveFormat.BitsPerSample / 8));
|
||||
_WaveFormat.Channels = _Channels;
|
||||
_WaveFormat.SamplesPerSecond = (int)(SampleRateKhz * 1000D);
|
||||
_WaveFormat.AverageBytesPerSecond =
|
||||
_WaveFormat.SamplesPerSecond *
|
||||
_WaveFormat.BlockAlignment;
|
||||
|
||||
_BufferPortionCount = 2;
|
||||
|
||||
_BufferDescription.BufferBytes = _BufferSize * sizeof(short) * _BufferPortionCount * _Channels;
|
||||
_BufferDescription.Format = _WaveFormat;
|
||||
_BufferDescription.WaveMapped = false;
|
||||
|
||||
_CaptureBuffer = new CaptureBuffer(_CaptureDevice, _BufferDescription);
|
||||
|
||||
_BufferPortionSize = _CaptureBuffer.SizeInBytes / _BufferPortionCount;
|
||||
_Notifications = new List<NotificationPosition>();
|
||||
|
||||
for (int i = 0; i < _BufferPortionCount; i++)
|
||||
{
|
||||
var notification = new NotificationPosition { Offset = _BufferPortionCount - 1 + (_BufferPortionSize * i), Event = new AutoResetEvent(false) };
|
||||
_Notifications.Add(notification);
|
||||
}
|
||||
|
||||
_CaptureBuffer.SetNotificationPositions(_Notifications.ToArray());
|
||||
_WaitHandles = new WaitHandle[_Notifications.Count];
|
||||
|
||||
for (int i = 0; i < _Notifications.Count; i++)
|
||||
_WaitHandles[i] = _Notifications[i].Event;
|
||||
|
||||
_CaptureThread = new Thread(_DoCapture) { Name = "DirectSoundCapture", IsBackground = true };
|
||||
|
||||
_Running = true;
|
||||
_CaptureThread.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_Running = false;
|
||||
|
||||
if (_CaptureThread != null)
|
||||
{
|
||||
_CaptureThread.Join();
|
||||
_CaptureThread = null;
|
||||
}
|
||||
|
||||
if (_CaptureBuffer != null)
|
||||
{
|
||||
_CaptureBuffer.Dispose();
|
||||
_CaptureBuffer = null;
|
||||
}
|
||||
|
||||
if (_Notifications != null)
|
||||
{
|
||||
foreach (NotificationPosition notification in _Notifications)
|
||||
notification.Event.Close();
|
||||
|
||||
_Notifications.Clear();
|
||||
_Notifications = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
{
|
||||
Stop();
|
||||
Start();
|
||||
}
|
||||
|
||||
private void _DoCapture()
|
||||
{
|
||||
int bufferPortionSamples = _BufferPortionSize / sizeof(byte);
|
||||
|
||||
// Buffer type must match this.waveFormat.FormatTag and this.waveFormat.BitsPerSample
|
||||
var bufferPortion = new byte[bufferPortionSamples];
|
||||
|
||||
_CaptureBuffer.Start(true);
|
||||
|
||||
while (_Running)
|
||||
{
|
||||
int bufferPortionIndex = WaitHandle.WaitAny(_WaitHandles);
|
||||
|
||||
_CaptureBuffer.Read(
|
||||
bufferPortion,
|
||||
0,
|
||||
bufferPortionSamples,
|
||||
_BufferPortionSize * Math.Abs((bufferPortionIndex - 1) % _BufferPortionCount));
|
||||
|
||||
SampleDataReady(this, new CSampleDataEventArgs(bufferPortion, _Guid));
|
||||
}
|
||||
|
||||
_CaptureBuffer.Stop();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
protected void Dispose(bool disposing)
|
||||
// ReSharper restore InconsistentNaming
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Stop();
|
||||
|
||||
if (_CaptureDevice != null)
|
||||
{
|
||||
_CaptureDevice.Dispose();
|
||||
_CaptureDevice = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,6 +212,9 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Base\CBackgroundMusic.cs" />
|
||||
<Compile Include="Lib\Sound\Playback\GstreamerSharp\CGstreamerSharpAudioStream.cs" />
|
||||
<Compile Include="Lib\Sound\Playback\OpenAL\COpenAlStream.cs" />
|
||||
<Compile Include="Lib\Sound\Playback\PortAudio\CPortAudioStream.cs" />
|
||||
<Compile Include="Lib\Sound\Record\CBuffer.cs" />
|
||||
<Compile Include="Base\CConfig.cs" />
|
||||
<Compile Include="Base\CCover.cs" />
|
||||
@@ -253,6 +256,8 @@
|
||||
<Compile Include="GameModes\CGameModes.cs" />
|
||||
<Compile Include="Lib\Input\CControllerFramework.cs" />
|
||||
<Compile Include="Lib\Sound\Playback\GstreamerSharp\CGstreamerSharpAudio.cs" />
|
||||
<Compile Include="Lib\Sound\Record\DirectSound\CSampleDataEventArgs.cs" />
|
||||
<Compile Include="Lib\Sound\Record\DirectSound\CSoundCardSource.cs" />
|
||||
<Compile Include="Lib\Video\Acinerella\CDecoder.cs" />
|
||||
<Compile Include="Lib\Video\Acinerella\CDecoderThread.cs" />
|
||||
<Compile Include="Lib\Video\Acinerella\CFramebuffer.cs" />
|
||||
|
||||
Reference in new issue
Block a user