Split classes of Lib/Sound

This commit is contained in:
Flamefire committed 2014-03-15 14:37:16 +01:00
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;
}
}
}
}
}
+5
View File
@@ -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" />