From 312c28ebed4b4472c224477e092803e1bd754c7f Mon Sep 17 00:00:00 2001 From: Flamefire Date: Sat, 15 Mar 2014 14:37:16 +0100 Subject: [PATCH] Split classes of Lib/Sound --- .../CGstreamerSharpAudioStream.cs | 288 ++++++++ .../Sound/Playback/OpenAL/COpenAlStream.cs | 504 ++++++++++++++ .../Playback/PortAudio/CPortAudioPlay.cs | 613 ----------------- .../Playback/PortAudio/CPortAudioStream.cs | 617 ++++++++++++++++++ .../Record/DirectSound/CDirectSoundRecord.cs | 183 +----- .../DirectSound/CSampleDataEventArgs.cs | 16 + .../Record/DirectSound/CSoundCardSource.cs | 175 +++++ Vocaluxe/Vocaluxe.csproj | 5 + 8 files changed, 1606 insertions(+), 795 deletions(-) create mode 100644 Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs create mode 100644 Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs create mode 100644 Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs create mode 100644 Vocaluxe/Lib/Sound/Record/DirectSound/CSampleDataEventArgs.cs create mode 100644 Vocaluxe/Lib/Sound/Record/DirectSound/CSoundCardSource.cs diff --git a/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs b/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs new file mode 100644 index 00000000..525061ac --- /dev/null +++ b/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs @@ -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; + } + } + } + } +} \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs new file mode 100644 index 00000000..16fac938 --- /dev/null +++ b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioPlay.cs b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioPlay.cs index c0cd93f8..38742b9f 100644 --- a/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioPlay.cs +++ b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioPlay.cs @@ -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); - } - } - } } \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs new file mode 100644 index 00000000..bf603b6d --- /dev/null +++ b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs @@ -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); + } + } + } +} \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Record/DirectSound/CDirectSoundRecord.cs b/Vocaluxe/Lib/Sound/Record/DirectSound/CDirectSoundRecord.cs index 484b0d6b..33ef285b 100644 --- a/Vocaluxe/Lib/Sound/Record/DirectSound/CDirectSoundRecord.cs +++ b/Vocaluxe/Lib/Sound/Record/DirectSound/CDirectSoundRecord.cs @@ -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 _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 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(); - - 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; - } - } - } - } } } \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Record/DirectSound/CSampleDataEventArgs.cs b/Vocaluxe/Lib/Sound/Record/DirectSound/CSampleDataEventArgs.cs new file mode 100644 index 00000000..695cd5a3 --- /dev/null +++ b/Vocaluxe/Lib/Sound/Record/DirectSound/CSampleDataEventArgs.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Record/DirectSound/CSoundCardSource.cs b/Vocaluxe/Lib/Sound/Record/DirectSound/CSoundCardSource.cs new file mode 100644 index 00000000..cb99d629 --- /dev/null +++ b/Vocaluxe/Lib/Sound/Record/DirectSound/CSoundCardSource.cs @@ -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 _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 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(); + + 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; + } + } + } + } +} \ No newline at end of file diff --git a/Vocaluxe/Vocaluxe.csproj b/Vocaluxe/Vocaluxe.csproj index e97608d2..4087a99e 100644 --- a/Vocaluxe/Vocaluxe.csproj +++ b/Vocaluxe/Vocaluxe.csproj @@ -212,6 +212,9 @@ + + + @@ -253,6 +256,8 @@ + +