Fix PortAudio to allow recording and playback with the same library using a base class the implements appropriate locking
This commit is contained in:
1 parent
e1f8bf4843
commit
f9d29b6141
6 files changed
+371
-318
No files matched your search
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using PortAudioSharp;
|
||||
using Vocaluxe.Base;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound
|
||||
{
|
||||
/// <summary>
|
||||
/// PortAudio can be used for record and playback
|
||||
/// So do some common stuff here and make sure those 2 do not interfere
|
||||
///
|
||||
/// DO NEVER use following Pa_* functions other than the ones from this class:
|
||||
/// Initialize, Terminate, OpenStream, CloseStream
|
||||
/// </summary>
|
||||
abstract class CPortAudioCommon
|
||||
{
|
||||
private static int _RefCount;
|
||||
private static readonly object _Mutex = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Safe method to init PortAudio (adds a reference)
|
||||
/// MUST call CloseDriver when done
|
||||
/// </summary>
|
||||
/// <returns>True on success, falso if not initialized (log written)</returns>
|
||||
protected static bool _InitDriver()
|
||||
{
|
||||
lock (_Mutex)
|
||||
{
|
||||
if (_RefCount == 0)
|
||||
{
|
||||
if (_CheckError("Initialize", PortAudio.Pa_Initialize()))
|
||||
return false;
|
||||
}
|
||||
_RefCount++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safe method to close PortAudio
|
||||
/// MUST be called _exactly_ once after successfull InitDriver call
|
||||
/// </summary>
|
||||
protected static void _CloseDriver()
|
||||
{
|
||||
lock (_Mutex)
|
||||
{
|
||||
Debug.Assert(_RefCount > 0);
|
||||
_RefCount--;
|
||||
if (_RefCount == 0)
|
||||
PortAudio.Pa_Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
protected static PortAudio.PaError _OpenStream(out IntPtr stream, ref PortAudio.PaStreamParameters? inputParameters, ref PortAudio.PaStreamParameters? outputParameters,
|
||||
double sampleRate, uint framesPerBuffer, PortAudio.PaStreamFlags streamFlags,
|
||||
PortAudio.PaStreamCallbackDelegate streamCallback, IntPtr userData)
|
||||
{
|
||||
lock (_Mutex)
|
||||
{
|
||||
return PortAudio.Pa_OpenStream(out stream, ref inputParameters, ref outputParameters, sampleRate, framesPerBuffer, streamFlags, streamCallback, userData);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to safely open an input stream and log potential error
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="inputParameters"></param>
|
||||
/// <param name="sampleRate"></param>
|
||||
/// <param name="framesPerBuffer"></param>
|
||||
/// <param name="streamFlags"></param>
|
||||
/// <param name="streamCallback"></param>
|
||||
/// <param name="userData"></param>
|
||||
/// <returns>True on success</returns>
|
||||
protected static bool _OpenInputStream(out IntPtr stream, ref PortAudio.PaStreamParameters? inputParameters,
|
||||
double sampleRate, uint framesPerBuffer, PortAudio.PaStreamFlags streamFlags,
|
||||
PortAudio.PaStreamCallbackDelegate streamCallback, IntPtr userData)
|
||||
{
|
||||
PortAudio.PaStreamParameters? outputParameters = null;
|
||||
return
|
||||
!_CheckError("OpenInputStream",
|
||||
_OpenStream(out stream, ref inputParameters, ref outputParameters, sampleRate, framesPerBuffer, streamFlags, streamCallback, userData));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to safely open an output stream and log potential error
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="outputParameters"></param>
|
||||
/// <param name="sampleRate"></param>
|
||||
/// <param name="framesPerBuffer"></param>
|
||||
/// <param name="streamFlags"></param>
|
||||
/// <param name="streamCallback"></param>
|
||||
/// <param name="userData"></param>
|
||||
/// <returns>True on success</returns>
|
||||
protected static bool _OpenOutputStream(out IntPtr stream, ref PortAudio.PaStreamParameters? outputParameters,
|
||||
double sampleRate, uint framesPerBuffer, PortAudio.PaStreamFlags streamFlags,
|
||||
PortAudio.PaStreamCallbackDelegate streamCallback, IntPtr userData)
|
||||
{
|
||||
PortAudio.PaStreamParameters? inputParameters = null;
|
||||
return
|
||||
!_CheckError("OpenOutputStream",
|
||||
_OpenStream(out stream, ref inputParameters, ref outputParameters, sampleRate, framesPerBuffer, streamFlags, streamCallback, userData));
|
||||
}
|
||||
|
||||
protected static void _CloseStream(IntPtr stream)
|
||||
{
|
||||
lock (_Mutex)
|
||||
{
|
||||
PortAudio.Pa_CloseStream(stream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if PA returned an error and logs it
|
||||
/// Returns true on error
|
||||
/// </summary>
|
||||
/// <param name="action">Action identifier (E.g. openStream)</param>
|
||||
/// <param name="errorCode">Result returned by Pa_* call</param>
|
||||
/// <returns>True on error</returns>
|
||||
protected static bool _CheckError(String action, PortAudio.PaError errorCode)
|
||||
{
|
||||
if (errorCode != PortAudio.PaError.paNoError)
|
||||
{
|
||||
CLog.LogError(action + " error: " + PortAudio.Pa_GetErrorText(errorCode));
|
||||
if (errorCode == PortAudio.PaError.paUnanticipatedHostError)
|
||||
{
|
||||
PortAudio.PaHostErrorInfo errorInfo = PortAudio.Pa_GetLastHostErrorInfo();
|
||||
CLog.LogError("- Host error API type: " + errorInfo.hostApiType);
|
||||
CLog.LogError("- Host error code: " + errorInfo.errorCode);
|
||||
CLog.LogError("- Host error text: " + errorInfo.errorText);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects the most appropriate host api
|
||||
/// </summary>
|
||||
/// <returns>The most appropriate host api</returns>
|
||||
protected static int _GetHostApi()
|
||||
{
|
||||
//Caller has to hold a reference anyway so no locking needed
|
||||
if (_RefCount < 1)
|
||||
return -1;
|
||||
|
||||
int selectedHostApi = PortAudio.Pa_GetDefaultHostApi();
|
||||
int apiCount = PortAudio.Pa_GetHostApiCount();
|
||||
for (int i = 0; i < apiCount; i++)
|
||||
{
|
||||
PortAudio.PaHostApiInfo apiInfo = PortAudio.Pa_GetHostApiInfo(i);
|
||||
if ((apiInfo.type == PortAudio.PaHostApiTypeId.paDirectSound)
|
||||
|| (apiInfo.type == PortAudio.PaHostApiTypeId.paALSA))
|
||||
selectedHostApi = i;
|
||||
}
|
||||
return selectedHostApi;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ namespace Vocaluxe.Lib.Sound.Playback
|
||||
}
|
||||
}
|
||||
|
||||
delegate void Closeproc(int streamID);
|
||||
delegate void Closeproc(int stream);
|
||||
|
||||
interface IPlayback
|
||||
{
|
||||
|
||||
@@ -15,42 +15,35 @@
|
||||
// along with Vocaluxe. If not, see <http://www.gnu.org/licenses/>.
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
{
|
||||
class CPortAudioPlay : IPlayback
|
||||
{
|
||||
private bool _Initialized;
|
||||
private readonly List<CPortAudioStream> _Decoder = new List<CPortAudioStream>();
|
||||
private Closeproc _Closeproc;
|
||||
private int _Count = 1;
|
||||
|
||||
private readonly Object _MutexDecoder = new Object();
|
||||
|
||||
private List<SAudioStreams> _Streams;
|
||||
private readonly List<CPortAudioStream> _Streams = new List<CPortAudioStream>();
|
||||
private int _NextID;
|
||||
|
||||
public bool Init()
|
||||
{
|
||||
if (_Initialized)
|
||||
CloseAll();
|
||||
|
||||
_Closeproc = _CloseProc;
|
||||
_Initialized = true;
|
||||
|
||||
_Streams = new List<SAudioStreams>();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CloseAll()
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
lock (_Streams)
|
||||
{
|
||||
for (int i = 0; i < _Decoder.Count; i++)
|
||||
_Decoder[i].Free(_Closeproc, i + 1);
|
||||
foreach (CPortAudioStream stream in _Streams)
|
||||
stream.Free();
|
||||
_Streams.Clear();
|
||||
}
|
||||
_Initialized = false;
|
||||
}
|
||||
|
||||
public void SetGlobalVolume(float volume)
|
||||
@@ -66,7 +59,7 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
if (!_Initialized)
|
||||
return 0;
|
||||
|
||||
lock (_MutexDecoder)
|
||||
lock (_Streams)
|
||||
{
|
||||
return _Streams.Count;
|
||||
}
|
||||
@@ -82,17 +75,16 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
|
||||
public int Load(string media, bool prescan)
|
||||
{
|
||||
var stream = new SAudioStreams(0);
|
||||
var decoder = new CPortAudioStream();
|
||||
//Note: We ignore prescan here as the stream does it always
|
||||
|
||||
if (decoder.Open(media) > -1)
|
||||
var stream = new CPortAudioStream(_NextID++, _CloseProc);
|
||||
|
||||
if (stream.Open(media))
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
lock (_Streams)
|
||||
{
|
||||
_Decoder.Add(decoder);
|
||||
stream.Handle = _Count++;
|
||||
_Streams.Add(stream);
|
||||
return stream.Handle;
|
||||
return stream.ID;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -100,12 +92,15 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
|
||||
public void Close(int stream)
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!_Initialized)
|
||||
return;
|
||||
lock (_Streams)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
int index = _GetStreamIndex(stream);
|
||||
if (index >= 0)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
_Decoder[_GetStreamIndex(stream)].Free(_Closeproc, stream);
|
||||
_Streams[index].Free();
|
||||
_Streams.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,216 +112,219 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
|
||||
public void Play(int stream, bool loop)
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!_Initialized)
|
||||
return;
|
||||
lock (_Streams)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
int index = _GetStreamIndex(stream);
|
||||
if (index >= 0)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
{
|
||||
_Decoder[_GetStreamIndex(stream)].Loop = loop;
|
||||
_Decoder[_GetStreamIndex(stream)].Play();
|
||||
}
|
||||
_Streams[index].Loop = loop;
|
||||
_Streams[index].Play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Pause(int stream)
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!_Initialized)
|
||||
return;
|
||||
lock (_Streams)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
_Decoder[_GetStreamIndex(stream)].Paused = true;
|
||||
}
|
||||
if (_AlreadyAdded(stream))
|
||||
_Streams[_GetStreamIndex(stream)].Paused = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop(int stream)
|
||||
{
|
||||
Pause(stream);
|
||||
if (!_Initialized)
|
||||
return;
|
||||
lock (_Streams)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
_Streams[_GetStreamIndex(stream)].Stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void Fade(int stream, float targetVolume, float seconds)
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!_Initialized)
|
||||
return;
|
||||
lock (_Streams)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
_Decoder[_GetStreamIndex(stream)].Fade(targetVolume, seconds);
|
||||
}
|
||||
if (_AlreadyAdded(stream))
|
||||
_Streams[_GetStreamIndex(stream)].Fade(targetVolume, seconds);
|
||||
}
|
||||
}
|
||||
|
||||
public void FadeAndPause(int stream, float targetVolume, float seconds)
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!_Initialized)
|
||||
return;
|
||||
lock (_Streams)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
_Decoder[_GetStreamIndex(stream)].FadeAndPause(targetVolume, seconds);
|
||||
}
|
||||
if (_AlreadyAdded(stream))
|
||||
_Streams[_GetStreamIndex(stream)].FadeAndPause(targetVolume, seconds);
|
||||
}
|
||||
}
|
||||
|
||||
public void FadeAndClose(int stream, float targetVolume, float seconds)
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!_Initialized)
|
||||
return;
|
||||
lock (_Streams)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
_Decoder[_GetStreamIndex(stream)].FadeAndClose(targetVolume, seconds, _Closeproc, stream);
|
||||
}
|
||||
if (_AlreadyAdded(stream))
|
||||
_Streams[_GetStreamIndex(stream)].FadeAndClose(targetVolume, seconds);
|
||||
}
|
||||
}
|
||||
|
||||
public void FadeAndStop(int stream, float targetVolume, float seconds)
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!_Initialized)
|
||||
return;
|
||||
lock (_Streams)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
_Decoder[_GetStreamIndex(stream)].FadeAndStop(targetVolume, seconds, _Closeproc, stream);
|
||||
}
|
||||
if (_AlreadyAdded(stream))
|
||||
_Streams[_GetStreamIndex(stream)].FadeAndStop(targetVolume, seconds);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetStreamVolume(int stream, float volume)
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!_Initialized)
|
||||
return;
|
||||
lock (_Streams)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
_Decoder[_GetStreamIndex(stream)].Volume = volume;
|
||||
}
|
||||
if (_AlreadyAdded(stream))
|
||||
_Streams[_GetStreamIndex(stream)].Volume = volume;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetStreamVolumeMax(int stream, float volume)
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!_Initialized)
|
||||
return;
|
||||
lock (_Streams)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
_Decoder[_GetStreamIndex(stream)].VolumeMax = volume;
|
||||
}
|
||||
if (_AlreadyAdded(stream))
|
||||
_Streams[_GetStreamIndex(stream)].VolumeMax = volume;
|
||||
}
|
||||
}
|
||||
|
||||
public float GetLength(int stream)
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!_Initialized)
|
||||
return 0f;
|
||||
lock (_Streams)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
return _Decoder[_GetStreamIndex(stream)].Length;
|
||||
}
|
||||
if (_AlreadyAdded(stream))
|
||||
return _Streams[_GetStreamIndex(stream)].Length;
|
||||
}
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public float GetPosition(int stream)
|
||||
{
|
||||
if (_Initialized)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
return _Decoder[_GetStreamIndex(stream)].Position;
|
||||
}
|
||||
|
||||
if (!_Initialized)
|
||||
return 0f;
|
||||
lock (_Streams)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
return _Streams[_GetStreamIndex(stream)].Position;
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public bool IsPlaying(int stream)
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!_Initialized)
|
||||
return false;
|
||||
lock (_Streams)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
return !_Decoder[_GetStreamIndex(stream)].Paused && !_Decoder[_GetStreamIndex(stream)].Finished;
|
||||
}
|
||||
if (_AlreadyAdded(stream))
|
||||
return !_Streams[_GetStreamIndex(stream)].Paused && !_Streams[_GetStreamIndex(stream)].Finished;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsPaused(int stream)
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!_Initialized)
|
||||
return false;
|
||||
lock (_Streams)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
return _Decoder[_GetStreamIndex(stream)].Paused;
|
||||
}
|
||||
if (_AlreadyAdded(stream))
|
||||
return _Streams[_GetStreamIndex(stream)].Paused;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsFinished(int stream)
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!_Initialized)
|
||||
return true;
|
||||
lock (_Streams)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
return _Decoder[_GetStreamIndex(stream)].Finished;
|
||||
}
|
||||
if (_AlreadyAdded(stream))
|
||||
return _Streams[_GetStreamIndex(stream)].Finished;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetPosition(int stream, float position)
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!_Initialized)
|
||||
return;
|
||||
lock (_Streams)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
{
|
||||
if (_AlreadyAdded(stream))
|
||||
_Decoder[_GetStreamIndex(stream)].Skip(position);
|
||||
}
|
||||
if (_AlreadyAdded(stream))
|
||||
_Streams[_GetStreamIndex(stream)].Skip(position);
|
||||
}
|
||||
}
|
||||
#endregion Stream Handling
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if strem with given id is found
|
||||
/// MUST hold _Stream-lock
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream id</param>
|
||||
/// <returns></returns>
|
||||
private bool _AlreadyAdded(int stream)
|
||||
{
|
||||
return _Streams.Any(st => st.Handle == stream);
|
||||
return _GetStreamIndex(stream) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the index of the stream with the given id
|
||||
/// MUST hold _Stream-lock
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream id</param>
|
||||
/// <returns></returns>
|
||||
private int _GetStreamIndex(int stream)
|
||||
{
|
||||
for (int i = 0; i < _Streams.Count; i++)
|
||||
{
|
||||
if (_Streams[i].Handle == stream)
|
||||
if (_Streams[i].ID == stream)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void _CloseProc(int streamID)
|
||||
/// <summary>
|
||||
/// Removes the stream with the given handle
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream handle</param>
|
||||
private void _CloseProc(int stream)
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!_Initialized)
|
||||
return;
|
||||
lock (_Streams)
|
||||
{
|
||||
lock (_MutexDecoder)
|
||||
{
|
||||
if (_AlreadyAdded(streamID))
|
||||
{
|
||||
int index = _GetStreamIndex(streamID);
|
||||
_Decoder.RemoveAt(index);
|
||||
_Streams.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
if (_AlreadyAdded(stream))
|
||||
_Streams.RemoveAt(_GetStreamIndex(stream));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,15 +8,15 @@ using VocaluxeLib;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
{
|
||||
class CPortAudioStream : IDisposable
|
||||
class CPortAudioStream : CPortAudioCommon, IDisposable
|
||||
{
|
||||
private const long _Bufsize = 1000000L;
|
||||
private const long _Beginrefill = 800000L;
|
||||
|
||||
public readonly int ID;
|
||||
private readonly Closeproc _Closeproc;
|
||||
|
||||
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;
|
||||
@@ -26,11 +26,9 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
|
||||
private static PortAudioSharp.PortAudio.PaHostApiInfo _ApiInfo;
|
||||
private static PortAudioSharp.PortAudio.PaDeviceInfo _OutputDeviceInfo;
|
||||
private IntPtr _Ptr = new IntPtr(0);
|
||||
private IntPtr _Stream = IntPtr.Zero;
|
||||
|
||||
private Closeproc _Closeproc;
|
||||
private PortAudioSharp.PortAudio.PaStreamCallbackDelegate _PaStreamCallback;
|
||||
private int _StreamID;
|
||||
private IAudioDecoder _Decoder;
|
||||
private float _BytesPerSecond;
|
||||
private bool _NoMoreData;
|
||||
@@ -61,8 +59,10 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
private readonly Object _LockData = new Object();
|
||||
private readonly Object _LockSyncSignals = new Object();
|
||||
|
||||
public CPortAudioStream()
|
||||
public CPortAudioStream(int id, Closeproc closeproc)
|
||||
{
|
||||
ID = id;
|
||||
_Closeproc = closeproc;
|
||||
_SyncTimer = new CSyncTimer(0f, 1f, 0.02f);
|
||||
}
|
||||
|
||||
@@ -71,10 +71,8 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Free(Closeproc closeProc, int streamID)
|
||||
public void Free()
|
||||
{
|
||||
_Closeproc = closeProc;
|
||||
_StreamID = streamID;
|
||||
_Terminated = true;
|
||||
}
|
||||
|
||||
@@ -135,6 +133,7 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
if (Finished)
|
||||
_SyncTimer.Pause();
|
||||
//TODO: Why not use Decoder.GetPosition()?
|
||||
//Decoder may return wrong timestamps. If you change this to exessive testing for monoton timestamps espacially for ogg files
|
||||
return _SyncTimer.Time;
|
||||
}
|
||||
}
|
||||
@@ -172,18 +171,14 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
_AfterFadeAction = EStreamAction.Pause;
|
||||
}
|
||||
|
||||
public void FadeAndClose(float targetVolume, float fadeTime, Closeproc closeProc, int streamID)
|
||||
public void FadeAndClose(float targetVolume, float fadeTime)
|
||||
{
|
||||
_Closeproc = closeProc;
|
||||
_StreamID = streamID;
|
||||
Fade(targetVolume, fadeTime);
|
||||
_AfterFadeAction = EStreamAction.Close;
|
||||
}
|
||||
|
||||
public void FadeAndStop(float targetVolume, float fadeTime, Closeproc closeProc, int streamID)
|
||||
public void FadeAndStop(float targetVolume, float fadeTime)
|
||||
{
|
||||
_Closeproc = closeProc;
|
||||
_StreamID = streamID;
|
||||
Fade(targetVolume, fadeTime);
|
||||
_AfterFadeAction = EStreamAction.Stop;
|
||||
}
|
||||
@@ -192,18 +187,12 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
{
|
||||
Paused = false;
|
||||
_AfterFadeAction = EStreamAction.Nothing;
|
||||
lock (_Mutex)
|
||||
{
|
||||
_ErrorCheck("StartStream", PortAudioSharp.PortAudio.Pa_StartStream(_Ptr));
|
||||
}
|
||||
_CheckError("StartStream", PortAudioSharp.PortAudio.Pa_StartStream(_Stream));
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
lock (_Mutex)
|
||||
{
|
||||
_ErrorCheck("StopStream (playback)", PortAudioSharp.PortAudio.Pa_StopStream(_Ptr));
|
||||
}
|
||||
_CheckError("StopStream (playback)", PortAudioSharp.PortAudio.Pa_StopStream(_Stream));
|
||||
Skip(0f);
|
||||
}
|
||||
|
||||
@@ -213,60 +202,57 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
set { _SetLoop = value; }
|
||||
}
|
||||
|
||||
public int Open(string fileName)
|
||||
public bool Open(string fileName)
|
||||
{
|
||||
if (_FileOpened)
|
||||
return -1;
|
||||
return false;
|
||||
|
||||
if (!File.Exists(fileName))
|
||||
return -1;
|
||||
|
||||
_Decoder = new CAudioDecoderFFmpeg();
|
||||
_Decoder.Init();
|
||||
return false;
|
||||
|
||||
bool driverInitialized = false;
|
||||
try
|
||||
{
|
||||
lock (_Mutex)
|
||||
{
|
||||
if (!_Initialized)
|
||||
{
|
||||
if (_ErrorCheck("Initialize", PortAudioSharp.PortAudio.Pa_Initialize()))
|
||||
return -1;
|
||||
_Initialized = true;
|
||||
if (!_InitDriver())
|
||||
return false;
|
||||
driverInitialized = true;
|
||||
|
||||
int hostApi = _ApiSelect();
|
||||
_ApiInfo = PortAudioSharp.PortAudio.Pa_GetHostApiInfo(hostApi);
|
||||
_OutputDeviceInfo = PortAudioSharp.PortAudio.Pa_GetDeviceInfo(_ApiInfo.defaultOutputDevice);
|
||||
if (_OutputDeviceInfo.defaultLowOutputLatency < 0.1)
|
||||
_OutputDeviceInfo.defaultLowOutputLatency = 0.1;
|
||||
}
|
||||
}
|
||||
int hostApi = _GetHostApi();
|
||||
_ApiInfo = PortAudioSharp.PortAudio.Pa_GetHostApiInfo(hostApi);
|
||||
_OutputDeviceInfo = PortAudioSharp.PortAudio.Pa_GetDeviceInfo(_ApiInfo.defaultOutputDevice);
|
||||
if (_OutputDeviceInfo.defaultLowOutputLatency < 0.1)
|
||||
_OutputDeviceInfo.defaultLowOutputLatency = 0.1;
|
||||
|
||||
_PaStreamCallback = _ProcessNewData;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_Initialized = false;
|
||||
if (driverInitialized)
|
||||
_CloseDriver();
|
||||
CLog.LogError("Error Init PortAudio Playback");
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
_Decoder = new CAudioDecoderFFmpeg();
|
||||
_Decoder.Init();
|
||||
_Decoder.Open(fileName);
|
||||
_Duration = _Decoder.GetLength();
|
||||
|
||||
SFormatInfo format = _Decoder.GetFormatInfo();
|
||||
if (format.SamplesPerSecond == 0)
|
||||
return -1;
|
||||
{
|
||||
_CloseDriver();
|
||||
_Decoder.Close();
|
||||
_Decoder = null;
|
||||
CLog.LogError("Error Init PortAudio Playback (samples=0)");
|
||||
return false;
|
||||
}
|
||||
|
||||
_Duration = _Decoder.GetLength();
|
||||
_ByteCount = 2 * format.ChannelCount;
|
||||
_BytesPerSecond = format.SamplesPerSecond * _ByteCount;
|
||||
_CurrentTime = 0f;
|
||||
_SyncTimer.Time = _CurrentTime;
|
||||
|
||||
var stream = new SAudioStreams(0);
|
||||
|
||||
var data = new IntPtr(0);
|
||||
|
||||
PortAudioSharp.PortAudio.PaStreamParameters? outputParams = new PortAudioSharp.PortAudio.PaStreamParameters
|
||||
{
|
||||
channelCount = format.ChannelCount,
|
||||
@@ -274,38 +260,33 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
sampleFormat = PortAudioSharp.PortAudio.PaSampleFormat.paInt16,
|
||||
suggestedLatency = _OutputDeviceInfo.defaultLowOutputLatency
|
||||
};
|
||||
PortAudioSharp.PortAudio.PaStreamParameters? inputParams = null;
|
||||
|
||||
var bufsize = (uint)CConfig.AudioBufferSize;
|
||||
lock (_Mutex)
|
||||
if (!_OpenOutputStream(
|
||||
out _Stream,
|
||||
ref outputParams,
|
||||
format.SamplesPerSecond,
|
||||
(uint)CConfig.AudioBufferSize,
|
||||
PortAudioSharp.PortAudio.PaStreamFlags.paNoFlag,
|
||||
_PaStreamCallback,
|
||||
IntPtr.Zero) || _Stream == IntPtr.Zero)
|
||||
{
|
||||
_ErrorCheck("OpenDefaultStream (playback)", PortAudioSharp.PortAudio.Pa_OpenStream(
|
||||
out _Ptr,
|
||||
ref inputParams,
|
||||
ref outputParams,
|
||||
format.SamplesPerSecond,
|
||||
bufsize,
|
||||
PortAudioSharp.PortAudio.PaStreamFlags.paNoFlag,
|
||||
_PaStreamCallback,
|
||||
data));
|
||||
_CloseDriver();
|
||||
_Decoder.Close();
|
||||
_Decoder = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
stream.Handle = _Ptr.ToInt32();
|
||||
//From now on closing the driver and the decoder is handled by the thread ONLY!
|
||||
|
||||
if (stream.Handle != 0)
|
||||
{
|
||||
_NumStreams++;
|
||||
_Paused = true;
|
||||
_Waiting = true;
|
||||
_FileOpened = true;
|
||||
_Data = new CRingBuffer(_Bufsize);
|
||||
_NoMoreData = false;
|
||||
_DecoderThread = new Thread(_Execute) {Priority = ThreadPriority.Normal, Name = Path.GetFileName(fileName)};
|
||||
_DecoderThread.Start();
|
||||
_Paused = true;
|
||||
_Waiting = true;
|
||||
_FileOpened = true;
|
||||
_Data = new CRingBuffer(_Bufsize);
|
||||
_NoMoreData = false;
|
||||
_DecoderThread = new Thread(_Execute) {Priority = ThreadPriority.Normal, Name = Path.GetFileName(fileName)};
|
||||
_DecoderThread.Start();
|
||||
|
||||
return stream.Handle;
|
||||
}
|
||||
return -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Skip(float time)
|
||||
@@ -374,9 +355,6 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
|
||||
private void _DoDecode()
|
||||
{
|
||||
if (!_FileOpened)
|
||||
return;
|
||||
|
||||
if (_Paused)
|
||||
return;
|
||||
|
||||
@@ -431,22 +409,11 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
|
||||
private void _DoFree()
|
||||
{
|
||||
if (_Initialized)
|
||||
{
|
||||
Stop();
|
||||
_NumStreams--;
|
||||
if (_NumStreams == 0)
|
||||
{
|
||||
lock (_Mutex)
|
||||
{
|
||||
PortAudioSharp.PortAudio.Pa_Terminate();
|
||||
_Initialized = false;
|
||||
}
|
||||
}
|
||||
_Decoder.Close();
|
||||
}
|
||||
_CloseStream(_Stream);
|
||||
_CloseDriver();
|
||||
_Decoder.Close();
|
||||
|
||||
_Closeproc(_StreamID);
|
||||
_Closeproc(ID);
|
||||
}
|
||||
#endregion Threading
|
||||
|
||||
@@ -517,44 +484,6 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
}
|
||||
#endregion Callbacks
|
||||
|
||||
private bool _ErrorCheck(String action, PortAudioSharp.PortAudio.PaError errorCode)
|
||||
{
|
||||
if (errorCode != PortAudioSharp.PortAudio.PaError.paNoError)
|
||||
{
|
||||
if (errorCode == PortAudioSharp.PortAudio.PaError.paStreamIsNotStopped)
|
||||
return false;
|
||||
|
||||
CLog.LogError(action + " error (playback): " + PortAudioSharp.PortAudio.Pa_GetErrorText(errorCode));
|
||||
if (errorCode == PortAudioSharp.PortAudio.PaError.paUnanticipatedHostError)
|
||||
{
|
||||
PortAudioSharp.PortAudio.PaHostErrorInfo errorInfo = PortAudioSharp.PortAudio.Pa_GetLastHostErrorInfo();
|
||||
CLog.LogError("- Host error API type: " + errorInfo.hostApiType);
|
||||
CLog.LogError("- Host error code: " + errorInfo.errorCode);
|
||||
CLog.LogError("- Host error text: " + errorInfo.errorText);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int _ApiSelect()
|
||||
{
|
||||
if (!_Initialized)
|
||||
return 0;
|
||||
|
||||
int selectedHostApi = PortAudioSharp.PortAudio.Pa_GetDefaultHostApi();
|
||||
int apiCount = PortAudioSharp.PortAudio.Pa_GetHostApiCount();
|
||||
for (int i = 0; i < apiCount; i++)
|
||||
{
|
||||
PortAudioSharp.PortAudio.PaHostApiInfo apiInfo = PortAudioSharp.PortAudio.Pa_GetHostApiInfo(i);
|
||||
if ((apiInfo.type == PortAudioSharp.PortAudio.PaHostApiTypeId.paDirectSound)
|
||||
|| (apiInfo.type == PortAudioSharp.PortAudio.PaHostApiTypeId.paALSA))
|
||||
selectedHostApi = i;
|
||||
}
|
||||
return selectedHostApi;
|
||||
}
|
||||
|
||||
private void _Update()
|
||||
{
|
||||
if (_Fading != null)
|
||||
|
||||
@@ -24,7 +24,7 @@ using Vocaluxe.Base;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||
{
|
||||
class CPortAudioRecord : IRecord
|
||||
class CPortAudioRecord : CPortAudioCommon, IRecord
|
||||
{
|
||||
private bool _Initialized;
|
||||
private readonly List<CRecordDevice> _Devices = new List<CRecordDevice>();
|
||||
@@ -47,16 +47,16 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||
/// <returns>true if success</returns>
|
||||
public bool Init()
|
||||
{
|
||||
if (_Initialized)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
if (_Initialized)
|
||||
Close();
|
||||
|
||||
if (_ErrorCheck("Initialize", PortAudioSharp.PortAudio.Pa_Initialize()))
|
||||
if (!_InitDriver())
|
||||
return false;
|
||||
|
||||
_Devices.Clear();
|
||||
int hostAPI = _ApiSelect();
|
||||
int hostAPI = _GetHostApi();
|
||||
int numDevices = PortAudioSharp.PortAudio.Pa_GetDeviceCount();
|
||||
for (int i = 0; i < numDevices; i++)
|
||||
{
|
||||
@@ -100,9 +100,6 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||
|
||||
Stop();
|
||||
|
||||
foreach (CBuffer buffer in _Buffer)
|
||||
buffer.Reset();
|
||||
|
||||
foreach (IntPtr handle in _RecHandle)
|
||||
{
|
||||
int waitcount = 0;
|
||||
@@ -113,6 +110,9 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||
}
|
||||
}
|
||||
|
||||
foreach (CBuffer buffer in _Buffer)
|
||||
buffer.Reset();
|
||||
|
||||
for (int i = 0; i < _RecHandle.Length; i++)
|
||||
_RecHandle[i] = IntPtr.Zero;
|
||||
|
||||
@@ -127,19 +127,17 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||
sampleFormat = PortAudioSharp.PortAudio.PaSampleFormat.paInt16,
|
||||
suggestedLatency = PortAudioSharp.PortAudio.Pa_GetDeviceInfo(_Devices[dev].ID).defaultLowInputLatency
|
||||
};
|
||||
PortAudioSharp.PortAudio.PaStreamParameters? outputParams = null;
|
||||
if (_ErrorCheck("OpenStream (rec)", PortAudioSharp.PortAudio.Pa_OpenStream(
|
||||
if (!_OpenInputStream(
|
||||
out _RecHandle[dev],
|
||||
ref inputParams,
|
||||
ref outputParams,
|
||||
44100,
|
||||
882,
|
||||
PortAudioSharp.PortAudio.PaStreamFlags.paNoFlag,
|
||||
_MyRecProc,
|
||||
new IntPtr(dev))))
|
||||
new IntPtr(dev)))
|
||||
return false;
|
||||
|
||||
if (_ErrorCheck("Start Stream (rec)", PortAudioSharp.PortAudio.Pa_StartStream(_RecHandle[dev])))
|
||||
if (_CheckError("Start Stream (rec)", PortAudioSharp.PortAudio.Pa_StartStream(_RecHandle[dev])))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -165,13 +163,14 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
Stop();
|
||||
if (!_Initialized)
|
||||
return;
|
||||
|
||||
if (_Initialized)
|
||||
{
|
||||
PortAudioSharp.PortAudio.Pa_Terminate();
|
||||
_Initialized = false;
|
||||
}
|
||||
foreach (IntPtr handle in _RecHandle)
|
||||
_CloseStream(handle);
|
||||
_CloseDriver();
|
||||
|
||||
_Initialized = false;
|
||||
|
||||
//System.IO.File.WriteAllBytes("test0.raw", _Buffer[0].Buffer);
|
||||
}
|
||||
@@ -312,40 +311,5 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||
|
||||
return PortAudioSharp.PortAudio.PaStreamCallbackResult.paContinue;
|
||||
}
|
||||
|
||||
private bool _ErrorCheck(String action, PortAudioSharp.PortAudio.PaError errorCode)
|
||||
{
|
||||
if (errorCode != PortAudioSharp.PortAudio.PaError.paNoError)
|
||||
{
|
||||
CLog.LogError(action + " error (rec): " + PortAudioSharp.PortAudio.Pa_GetErrorText(errorCode));
|
||||
if (errorCode == PortAudioSharp.PortAudio.PaError.paUnanticipatedHostError)
|
||||
{
|
||||
PortAudioSharp.PortAudio.PaHostErrorInfo errorInfo = PortAudioSharp.PortAudio.Pa_GetLastHostErrorInfo();
|
||||
CLog.LogError("- Host error API type: " + errorInfo.hostApiType);
|
||||
CLog.LogError("- Host error code: " + errorInfo.errorCode);
|
||||
CLog.LogError("- Host error text: " + errorInfo.errorText);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int _ApiSelect()
|
||||
{
|
||||
if (!_Initialized)
|
||||
return 0;
|
||||
|
||||
int selectedHostApi = PortAudioSharp.PortAudio.Pa_GetDefaultHostApi();
|
||||
int apiCount = PortAudioSharp.PortAudio.Pa_GetHostApiCount();
|
||||
for (int i = 0; i < apiCount; i++)
|
||||
{
|
||||
PortAudioSharp.PortAudio.PaHostApiInfo apiInfo = PortAudioSharp.PortAudio.Pa_GetHostApiInfo(i);
|
||||
if ((apiInfo.type == PortAudioSharp.PortAudio.PaHostApiTypeId.paDirectSound)
|
||||
|| (apiInfo.type == PortAudioSharp.PortAudio.PaHostApiTypeId.paALSA))
|
||||
selectedHostApi = i;
|
||||
}
|
||||
return selectedHostApi;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,6 +221,7 @@
|
||||
<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\CPortAudioCommon.cs" />
|
||||
<Compile Include="Lib\Sound\Record\CBuffer.cs" />
|
||||
<Compile Include="Base\CConfig.cs" />
|
||||
<Compile Include="Base\CCover.cs" />
|
||||
|
||||
Reference in new issue
Block a user