* Handling for BufferLost Fixes issue #1212 where adding/removing headphones would lead to a crash by restoring the sound device when it's detected and being defensive with exception handling. * Internalize the handling of buffer lost and make it a bit more efficient. Remove interface function for SoundLost and move the logic to soley live inside DirectSoundOutput. Additionally I discovered I did not need to tear down the entire device to restore sound, typically Restore() and Play() handle it. Still need to wrap every place that can throw an exception in a try/catch block and wait for WriteSamples to handle it. * Update DirectSoundSoundOutput.cs Logic is hard. * Retry limiter added to recovery Added a self reducing retry counter that will try to start sound 5 -> 4 -> 3 -> 2 -> 1 times each time it tries to recover until it succeeds, at which point the counter returns to 5. This allows for quicker attempts at recovery without the risk of an infinite loop or terrible performance from sleeping 10 ms.
This commit is contained in:
parent
312f029b0b
commit
46e744cd33
|
@ -18,10 +18,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
private int _filledBufferSizeBytes;
|
||||
private long _lastWriteTime;
|
||||
private int _lastWriteCursor;
|
||||
private int _retryCounter;
|
||||
|
||||
public DirectSoundSoundOutput(Sound sound, IntPtr mainWindowHandle, string soundDevice)
|
||||
{
|
||||
_sound = sound;
|
||||
_retryCounter = 5;
|
||||
|
||||
var deviceInfo = DirectSound.GetDevices().FirstOrDefault(d => d.Description == soundDevice);
|
||||
_device = deviceInfo != null ? new DirectSound(deviceInfo.DriverGuid) : new DirectSound();
|
||||
|
@ -49,26 +51,22 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public int MaxSamplesDeficit { get; private set; }
|
||||
|
||||
public void ApplyVolumeSettings(double volume)
|
||||
private bool IsPlaying => _deviceBuffer != null && (_deviceBuffer.Status & BufferStatus.BufferLost) == 0 && (_deviceBuffer.Status & BufferStatus.Playing) == BufferStatus.Playing;
|
||||
|
||||
private void StartPlaying()
|
||||
{
|
||||
// I'm not sure if this is "technically" correct but it works okay
|
||||
int range = (int)Volume.Maximum - (int)Volume.Minimum;
|
||||
_deviceBuffer.Volume = (int)(Math.Pow(volume, 0.1) * range) + (int)Volume.Minimum;
|
||||
}
|
||||
|
||||
public void StartSound()
|
||||
_actualWriteOffsetBytes = -1;
|
||||
_filledBufferSizeBytes = 0;
|
||||
_lastWriteTime = 0;
|
||||
_lastWriteCursor = 0;
|
||||
int attempts = _retryCounter;
|
||||
while (!IsPlaying && attempts > 0)
|
||||
{
|
||||
attempts--;
|
||||
try
|
||||
{
|
||||
if (_deviceBuffer == null)
|
||||
{
|
||||
BufferSizeSamples = Sound.MillisecondsToSamples(GlobalWin.Config.SoundBufferSizeMs);
|
||||
|
||||
// 35 to 65 milliseconds depending on how big the buffer is. This is a trade-off
|
||||
// between more frequent but less severe glitches (i.e. catching underruns before
|
||||
// they happen and filling the buffer with silence) or less frequent but more
|
||||
// severe glitches. At least on my Windows 8 machines, the distance between the
|
||||
// play and write cursors can be up to 30 milliseconds, so that would be the
|
||||
// absolute minimum we could use here.
|
||||
int minBufferFullnessMs = Math.Min(35 + ((GlobalWin.Config.SoundBufferSizeMs - 60) / 2), 65);
|
||||
MaxSamplesDeficit = BufferSizeSamples - Sound.MillisecondsToSamples(minBufferFullnessMs);
|
||||
|
||||
var format = new WaveFormat
|
||||
{
|
||||
SamplesPerSecond = Sound.SampleRate,
|
||||
|
@ -91,28 +89,89 @@ namespace BizHawk.Client.EmuHawk
|
|||
};
|
||||
|
||||
_deviceBuffer = new SecondarySoundBuffer(_device, desc);
|
||||
|
||||
_actualWriteOffsetBytes = -1;
|
||||
_filledBufferSizeBytes = 0;
|
||||
_lastWriteTime = 0;
|
||||
_lastWriteCursor = 0;
|
||||
}
|
||||
|
||||
_deviceBuffer.Play(0, PlayFlags.Looping);
|
||||
}
|
||||
catch (DirectSoundException)
|
||||
{
|
||||
if (_deviceBuffer != null)
|
||||
{
|
||||
_deviceBuffer.Restore();
|
||||
}
|
||||
if (attempts > 0)
|
||||
{
|
||||
System.Threading.Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IsPlaying)
|
||||
{
|
||||
_retryCounter = 5;
|
||||
}
|
||||
else if (_retryCounter > 1)
|
||||
{
|
||||
_retryCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyVolumeSettings(double volume)
|
||||
{
|
||||
if (IsPlaying)
|
||||
{
|
||||
try
|
||||
{
|
||||
// I'm not sure if this is "technically" correct but it works okay
|
||||
int range = (int)Volume.Maximum - (int)Volume.Minimum;
|
||||
_deviceBuffer.Volume = (int)(Math.Pow(volume, 0.1) * range) + (int)Volume.Minimum;
|
||||
}
|
||||
catch (DirectSoundException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void StartSound()
|
||||
{
|
||||
BufferSizeSamples = Sound.MillisecondsToSamples(GlobalWin.Config.SoundBufferSizeMs);
|
||||
|
||||
// 35 to 65 milliseconds depending on how big the buffer is. This is a trade-off
|
||||
// between more frequent but less severe glitches (i.e. catching underruns before
|
||||
// they happen and filling the buffer with silence) or less frequent but more
|
||||
// severe glitches. At least on my Windows 8 machines, the distance between the
|
||||
// play and write cursors can be up to 30 milliseconds, so that would be the
|
||||
// absolute minimum we could use here.
|
||||
int minBufferFullnessMs = Math.Min(35 + ((GlobalWin.Config.SoundBufferSizeMs - 60) / 2), 65);
|
||||
MaxSamplesDeficit = BufferSizeSamples - Sound.MillisecondsToSamples(minBufferFullnessMs);
|
||||
|
||||
StartPlaying();
|
||||
}
|
||||
|
||||
public void StopSound()
|
||||
{
|
||||
if (IsPlaying)
|
||||
{
|
||||
try
|
||||
{
|
||||
_deviceBuffer.Stop();
|
||||
}
|
||||
catch (DirectSoundException)
|
||||
{
|
||||
}
|
||||
}
|
||||
_deviceBuffer.Dispose();
|
||||
_deviceBuffer = null;
|
||||
|
||||
BufferSizeSamples = 0;
|
||||
}
|
||||
|
||||
public int CalculateSamplesNeeded()
|
||||
{
|
||||
if (_deviceBuffer.Status == BufferStatus.BufferLost) return 0;
|
||||
|
||||
int samplesNeeded = 0;
|
||||
if (IsPlaying)
|
||||
{
|
||||
try
|
||||
{
|
||||
long currentWriteTime = Stopwatch.GetTimestamp();
|
||||
int playCursor = _deviceBuffer.CurrentPlayPosition;
|
||||
int writeCursor = _deviceBuffer.CurrentWritePosition;
|
||||
|
@ -132,13 +191,19 @@ namespace BizHawk.Client.EmuHawk
|
|||
_actualWriteOffsetBytes = writeCursor;
|
||||
_filledBufferSizeBytes = 0;
|
||||
}
|
||||
int samplesNeeded = CircularDistance(_actualWriteOffsetBytes, playCursor, BufferSizeBytes) / Sound.BlockAlign;
|
||||
samplesNeeded = CircularDistance(_actualWriteOffsetBytes, playCursor, BufferSizeBytes) / Sound.BlockAlign;
|
||||
if (isInitializing || detectedUnderrun)
|
||||
{
|
||||
_sound.HandleInitializationOrUnderrun(detectedUnderrun, ref samplesNeeded);
|
||||
}
|
||||
_lastWriteTime = currentWriteTime;
|
||||
_lastWriteCursor = writeCursor;
|
||||
}
|
||||
catch (DirectSoundException)
|
||||
{
|
||||
samplesNeeded = 0;
|
||||
}
|
||||
}
|
||||
return samplesNeeded;
|
||||
}
|
||||
|
||||
|
@ -148,11 +213,32 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
|
||||
public void WriteSamples(short[] samples, int sampleOffset, int sampleCount)
|
||||
{
|
||||
// For lack of a better place, this function will be the one that attempts to restart playing
|
||||
// after a sound buffer is lost.
|
||||
if (IsPlaying)
|
||||
{
|
||||
if (sampleCount == 0) return;
|
||||
try
|
||||
{
|
||||
_deviceBuffer.Write(samples, sampleOffset * Sound.ChannelCount, sampleCount * Sound.ChannelCount, _actualWriteOffsetBytes, LockFlags.None);
|
||||
_actualWriteOffsetBytes = (_actualWriteOffsetBytes + (sampleCount * Sound.BlockAlign)) % BufferSizeBytes;
|
||||
_filledBufferSizeBytes += sampleCount * Sound.BlockAlign;
|
||||
}
|
||||
catch (DirectSoundException)
|
||||
{
|
||||
_deviceBuffer.Restore();
|
||||
StartPlaying();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_deviceBuffer != null)
|
||||
{
|
||||
_deviceBuffer.Restore();
|
||||
}
|
||||
StartPlaying();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue