Fix PortAudio stream cleanup on shutdown and restart (#860)

* Crash Fix

* Crash Fix

* Crash Fix

* Crash Fix
This commit is contained in:
McSchneidar authored and GitHub committed 2026-04-08 08:13:42 +02:00
1 parent 3bc71b8241
commit 0b6bc14563
2 files changed
+187 -36

No files matched your search

+52 -11
View File
@@ -64,21 +64,30 @@ namespace Vocaluxe.Lib.Sound
{
if (_Disposed)
return;
if (!disposing)
CLog.Debug("Did not close CPortAudioHandle");
//Make sure we do not leek any streams as we may keep PA open
if (_Streams.Count > 0)
IntPtr[] streamsToClose;
lock (_Mutex)
{
CLog.Debug("Did not close " + _Streams.Count + "PortAudio-Stream(s)");
while (_Streams.Count > 0)
CloseStream(_Streams[0]);
streamsToClose = _Streams.ToArray();
}
if (streamsToClose.Length > 0)
CLog.Debug("Did not close " + streamsToClose.Length + " PortAudio-Stream(s)");
foreach (IntPtr stream in streamsToClose)
CloseStream(stream);
lock (_Mutex)
{
if (_Disposed)
return;
Debug.Assert(_RefCount > 0);
_RefCount--;
if (_RefCount == 0)
{
try
@@ -90,6 +99,7 @@ namespace Vocaluxe.Lib.Sound
CLog.Error(ex, "Error disposing PortAudio");
}
}
_Disposed = true;
}
}
@@ -172,22 +182,53 @@ namespace Vocaluxe.Lib.Sound
lock (_Mutex)
{
if (_Disposed)
throw new ObjectDisposedException("PortAudioHandle already disposed");
return;
if (stream == IntPtr.Zero)
{
CLog.Debug("Stream is null, skipping close.");
return;
}
bool wasTracked = _Streams.Remove(stream);
if (!wasTracked)
{
CLog.Debug("Stream was already removed or never tracked, skipping duplicate close.");
return;
}
try
{
if (stream == IntPtr.Zero)
try
{
CLog.Debug("Stream is null, skipping close.");
return;
PortAudio.PaError isStoppedResult = PortAudio.Pa_IsStreamStopped(stream);
if (isStoppedResult == PortAudio.PaError.paStreamIsNotStopped)
{
PortAudio.PaError stopResult = PortAudio.Pa_StopStream(stream);
if (stopResult != PortAudio.PaError.paNoError &&
stopResult != PortAudio.PaError.paStreamIsStopped)
{
CLog.Error("StopStream before close failed: " + PortAudio.Pa_GetErrorText(stopResult));
}
}
}
PortAudio.Pa_CloseStream(stream);
catch (Exception ex)
{
CLog.Error(ex, "Error stopping stream before close:");
}
PortAudio.PaError closeResult = PortAudio.Pa_CloseStream(stream);
if (closeResult != PortAudio.PaError.paNoError)
CLog.Error("CloseStream error: " + PortAudio.Pa_GetErrorText(closeResult));
}
catch (AccessViolationException ex)
{
CLog.Error(ex, "Access violation while closing PortAudio stream.");
}
catch (Exception ex)
{
CLog.Error(ex, "Error closing stream:");
}
_Streams.Remove(stream);
}
}
@@ -74,7 +74,7 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
}
/// <summary>
/// Start Voice Capturing
/// Voice Capturing
/// </summary>
/// <returns></returns>
public bool Start()
@@ -84,32 +84,65 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
Stop();
foreach (IntPtr handle in _RecHandle)
if (_RecHandle != null && _PaHandle != null)
{
int waitcount = 0;
while (waitcount < 5 && PortAudioSharp.PortAudio.Pa_IsStreamStopped(handle) == PortAudioSharp.PortAudio.PaError.paStreamIsNotStopped)
for (int i = 0; i < _RecHandle.Length; i++)
{
Thread.Sleep(1);
waitcount++;
IntPtr handle = _RecHandle[i];
if (handle == IntPtr.Zero)
continue;
int waitcount = 0;
while (waitcount < 5)
{
try
{
if (PortAudioSharp.PortAudio.Pa_IsStreamStopped(handle) !=
PortAudioSharp.PortAudio.PaError.paStreamIsNotStopped)
break;
}
catch (Exception ex)
{
CLog.Error(ex, "Error while waiting for PortAudio stream to stop:");
break;
}
Thread.Sleep(1);
waitcount++;
}
try
{
_PaHandle.CloseStream(handle);
}
catch (Exception ex)
{
CLog.Error(ex, "Error closing old PortAudio record stream before restart:");
}
finally
{
_RecHandle[i] = IntPtr.Zero;
}
}
}
foreach (CBuffer buffer in _Buffer)
buffer.Reset();
for (int i = 0; i < _RecHandle.Length; i++)
_RecHandle[i] = IntPtr.Zero;
for (int dev = 0; dev < _Devices.Count; dev++)
{
bool usingDevice = false;
for (int ch = 0; ch < _Devices[dev].Channels; ++ch) {
for (int ch = 0; ch < _Devices[dev].Channels; ++ch)
{
if (_Devices[dev].PlayerChannel[ch] > 0)
usingDevice = true;
}
if (usingDevice)
{
PortAudioSharp.PortAudio.PaStreamParameters? inputParams = new PortAudioSharp.PortAudio.PaStreamParameters
if (!usingDevice)
continue;
PortAudioSharp.PortAudio.PaStreamParameters? inputParams =
new PortAudioSharp.PortAudio.PaStreamParameters
{
channelCount = _Devices[dev].Channels,
device = _Devices[dev].ID,
@@ -117,7 +150,8 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
suggestedLatency = PortAudioSharp.PortAudio.Pa_GetDeviceInfo(_Devices[dev].ID).defaultLowInputLatency,
hostApiSpecificStreamInfo = IntPtr.Zero
};
if (!_PaHandle.OpenInputStream(
if (!_PaHandle.OpenInputStream(
out _RecHandle[dev],
ref inputParams,
44100,
@@ -125,12 +159,52 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
PortAudioSharp.PortAudio.PaStreamFlags.paNoFlag,
_MyRecProc,
new IntPtr(dev)))
return false;
{
for (int j = 0; j < _RecHandle.Length; j++)
{
if (_RecHandle[j] == IntPtr.Zero)
continue;
if (_PaHandle.CheckError("Start Stream (rec)", PortAudioSharp.PortAudio.Pa_StartStream(_RecHandle[dev])))
return false;
try
{
_PaHandle.CloseStream(_RecHandle[j]);
}
catch (Exception ex)
{
CLog.Error(ex, "Error rolling back PortAudio stream after open failure:");
}
finally
{
_RecHandle[j] = IntPtr.Zero;
}
}
return false;
}
if (_PaHandle.CheckError("Start Stream (rec)", PortAudioSharp.PortAudio.Pa_StartStream(_RecHandle[dev])))
{
for (int j = 0; j < _RecHandle.Length; j++)
{
if (_RecHandle[j] == IntPtr.Zero)
continue;
try
{
_PaHandle.CloseStream(_RecHandle[j]);
}
catch (Exception ex)
{
CLog.Error(ex, "Error rolling back PortAudio stream after start failure:");
}
finally
{
_RecHandle[j] = IntPtr.Zero;
}
}
return false;
}
}
return true;
}
@@ -142,16 +216,34 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
{
if (!_Initialized)
return false;
if (_RecHandle == null)
return true;
foreach (IntPtr handle in _RecHandle)
{
if (handle != IntPtr.Zero)
if (handle == IntPtr.Zero)
continue;
try
{
PortAudioSharp.PortAudio.Pa_StopStream(handle);
PortAudioSharp.PortAudio.Pa_CloseStream(handle);
PortAudioSharp.PortAudio.PaError isStoppedResult = PortAudioSharp.PortAudio.Pa_IsStreamStopped(handle);
if (isStoppedResult == PortAudioSharp.PortAudio.PaError.paStreamIsNotStopped)
{
PortAudioSharp.PortAudio.PaError stopResult = PortAudioSharp.PortAudio.Pa_StopStream(handle);
if (stopResult != PortAudioSharp.PortAudio.PaError.paNoError &&
stopResult != PortAudioSharp.PortAudio.PaError.paStreamIsStopped)
{
CLog.Error("StopStream error: " + PortAudioSharp.PortAudio.Pa_GetErrorText(stopResult));
}
}
}
catch (Exception ex)
{
CLog.Error(ex, "Error stopping PortAudio record stream:");
}
}
_RecHandle = new IntPtr[_Devices.Count];
return true;
}
@@ -162,16 +254,34 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
{
if (_RecHandle != null && _RecHandle.Length > 0)
{
foreach (IntPtr handle in _RecHandle)
Stop();
if (_PaHandle != null)
{
if (handle != IntPtr.Zero)
for (int i = 0; i < _RecHandle.Length; i++)
{
_PaHandle.CloseStream(handle);
IntPtr handle = _RecHandle[i];
if (handle == IntPtr.Zero)
continue;
try
{
_PaHandle.CloseStream(handle);
}
catch (Exception ex)
{
CLog.Error(ex, "Error closing PortAudio record stream:");
}
finally
{
_RecHandle[i] = IntPtr.Zero;
}
}
}
_RecHandle = new IntPtr[_Devices.Count];
}
if (_PaHandle != null)
{
_PaHandle.Close();
@@ -214,4 +324,4 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
return PortAudioSharp.PortAudio.PaStreamCallbackResult.paContinue;
}
}
}
}