Allow CPU throttle to catch up for more than 3 missed frames if the user has configured a large sound buffer size.

SoundOutputProvider: Better workaround for cores whose audio output goes dead (0 samples) at times. Better underrun handling. Different threshold for samples surplus vs. deficit.
This commit is contained in:
jdpurcell 2015-01-28 00:53:49 +00:00
parent 09072acb3b
commit 58cd9796f2
4 changed files with 70 additions and 20 deletions

View File

@ -22,6 +22,11 @@ namespace BizHawk.Client.Common
/// </summary>
public static bool DisableSecondaryThrottling;
/// <summary>
/// How far the sound output buffer can go below full before drastic corrective measures are taken.
/// </summary>
public static int SoundMaxBufferDeficitMs;
public static Controller NullControls;
public static AutofireController AutofireNullControls;

View File

@ -124,14 +124,17 @@ namespace BizHawk.Client.EmuHawk
_lastWriteTime = 0;
_lastWriteCursor = 0;
int minBufferFullnessSamples = MillisecondsToSamples(
int minBufferFullnessMs =
Global.Config.SoundBufferSizeMs < 80 ? 35 :
Global.Config.SoundBufferSizeMs < 100 ? 45 :
55);
55;
_outputProvider = new SoundOutputProvider();
_outputProvider.MaxSamplesDeficit = BufferSizeSamples - minBufferFullnessSamples;
_outputProvider.MaxSamplesDeficit = BufferSizeSamples - MillisecondsToSamples(minBufferFullnessMs);
_outputProvider.BaseSoundProvider = _syncSoundProvider;
Global.SoundMaxBufferDeficitMs = Global.Config.SoundBufferSizeMs - minBufferFullnessMs;
//LogUnderruns = true;
//_outputProvider.LogDebug = true;
}
@ -147,6 +150,8 @@ namespace BizHawk.Client.EmuHawk
_outputProvider = null;
Global.SoundMaxBufferDeficitMs = 0;
BufferSizeSamples = 0;
}
@ -224,6 +229,7 @@ namespace BizHawk.Client.EmuHawk
{
if (LogUnderruns) Console.WriteLine("DirectSound underrun detected!");
detectedUnderrun = true;
_outputProvider.OnUnderrun();
}
}
bool isInitializing = _actualWriteOffsetBytes == -1;

View File

@ -21,15 +21,12 @@ namespace BizHawk.Client.EmuHawk
private const int SampleRate = 44100;
private const int ChannelCount = 2;
private const int SoftCorrectionThresholdSamples = 5 * SampleRate / 1000;
private const int StartupHardCorrectionThresholdSamples = 10 * SampleRate / 1000;
// Can't go more than 50 ms or so even if there's enough buffered up in the device
// because the clock throttle loses its "rubber band" effect and won't replenish
// our buffer. At that point it's better to hard correct than to let the soft
// correction cause a major pitch shift.
private const int NormalHardCorrectionThresholdSamples = 50 * SampleRate / 1000;
private const int StartupMaxSamplesSurplusDeficit = 10 * SampleRate / 1000;
private const int MaxSamplesSurplus = 50 * SampleRate / 1000;
private const int UsableHistoryLength = 20;
private const int MaxHistoryLength = 60;
private const int SoftCorrectionLength = 240;
private const int BaseMaxConsecutiveEmptyFrames = 1;
private const int BaseSampleRateUsableHistoryLength = 60;
private const int BaseSampleRateMaxHistoryLength = 300;
private const int MinResamplingDistanceSamples = 3;
@ -40,6 +37,9 @@ namespace BizHawk.Client.EmuHawk
private Queue<int> _outputCountHistory = new Queue<int>();
private Queue<bool> _hardCorrectionHistory = new Queue<bool>();
private int _baseConsecutiveEmptyFrames;
private Queue<bool> _baseEmptyFrameCorrectionHistory = new Queue<bool>();
private double _lastAdvertisedSamplesPerFrame;
private Queue<int> _baseSamplesPerFrame = new Queue<int>();
@ -62,6 +62,8 @@ namespace BizHawk.Client.EmuHawk
_extraCountHistory.Clear();
_outputCountHistory.Clear();
_hardCorrectionHistory.Clear();
_baseConsecutiveEmptyFrames = 0;
_baseEmptyFrameCorrectionHistory.Clear();
_lastAdvertisedSamplesPerFrame = 0.0;
_baseSamplesPerFrame.Clear();
_outputBuffer = new short[0];
@ -74,6 +76,13 @@ namespace BizHawk.Client.EmuHawk
}
}
public void OnUnderrun()
{
_extraCountHistory.Clear();
_outputCountHistory.Clear();
_hardCorrectionHistory.Clear();
}
public bool LogDebug { get; set; }
private double AdvertisedSamplesPerFrame
@ -87,7 +96,7 @@ namespace BizHawk.Client.EmuHawk
if (_extraCountHistory.Count >= UsableHistoryLength && !_hardCorrectionHistory.Any(c => c))
{
double offsetFromTarget = _extraCountHistory.Average();
double offsetFromTarget = CalculatePowerMean(_extraCountHistory, 0.6);
if (Math.Abs(offsetFromTarget) > SoftCorrectionThresholdSamples)
{
double correctionSpan = _outputCountHistory.Average() * SoftCorrectionLength;
@ -99,12 +108,13 @@ namespace BizHawk.Client.EmuHawk
int bufferSampleCount = _buffer.Count / ChannelCount;
int extraSampleCount = bufferSampleCount - idealSampleCount;
int hardCorrectionThresholdSamples = _extraCountHistory.Count >= UsableHistoryLength ?
Math.Min(NormalHardCorrectionThresholdSamples, MaxSamplesDeficit) :
Math.Min(StartupHardCorrectionThresholdSamples, MaxSamplesDeficit);
int maxSamplesDeficit = _extraCountHistory.Count >= UsableHistoryLength ?
MaxSamplesDeficit : Math.Min(StartupMaxSamplesSurplusDeficit, MaxSamplesDeficit);
int maxSamplesSurplus = _extraCountHistory.Count >= UsableHistoryLength ?
MaxSamplesSurplus : Math.Min(StartupMaxSamplesSurplusDeficit, MaxSamplesSurplus);
bool hardCorrected = false;
if (extraSampleCount < -hardCorrectionThresholdSamples)
if (extraSampleCount < -maxSamplesDeficit)
{
int generateSampleCount = -extraSampleCount;
if (LogDebug) Console.WriteLine("Generating " + generateSampleCount + " samples");
@ -114,7 +124,7 @@ namespace BizHawk.Client.EmuHawk
}
hardCorrected = true;
}
else if (extraSampleCount > hardCorrectionThresholdSamples)
else if (extraSampleCount > maxSamplesSurplus)
{
int discardSampleCount = extraSampleCount;
if (LogDebug) Console.WriteLine("Discarding " + discardSampleCount + " samples");
@ -136,8 +146,8 @@ namespace BizHawk.Client.EmuHawk
if (LogDebug)
{
Console.WriteLine("Avg: {0:0.0} ms, Min: {1:0.0} ms, Max: {2:0.0} ms, Scale: {3:0.0000}",
_extraCountHistory.Average() * 1000.0 / SampleRate,
Console.WriteLine("Avg: {0:0.0} ms, Min: {1:0.0}, Max: {2:0.0}, Scale: {3:0.0000}",
CalculatePowerMean(_extraCountHistory, 0.6) * 1000.0 / SampleRate,
_extraCountHistory.Min() * 1000.0 / SampleRate,
_extraCountHistory.Max() * 1000.0 / SampleRate,
scaleFactor);
@ -155,6 +165,24 @@ namespace BizHawk.Client.EmuHawk
BaseSoundProvider.GetSamples(out samples, out count);
bool correctedEmptyFrame = false;
if (count == 0)
{
_baseConsecutiveEmptyFrames++;
if (_baseConsecutiveEmptyFrames > BaseMaxConsecutiveEmptyFrames)
{
int silenceCount = (int)Math.Round(AdvertisedSamplesPerFrame);
samples = Resample(samples, count, silenceCount);
count = silenceCount;
correctedEmptyFrame = true;
}
}
else if (_baseConsecutiveEmptyFrames != 0)
{
_baseConsecutiveEmptyFrames = 0;
}
UpdateHistory(_baseEmptyFrameCorrectionHistory, correctedEmptyFrame, MaxHistoryLength);
if (AdvertisedSamplesPerFrame != _lastAdvertisedSamplesPerFrame)
{
_baseSamplesPerFrame.Clear();
@ -162,7 +190,8 @@ namespace BizHawk.Client.EmuHawk
}
UpdateHistory(_baseSamplesPerFrame, count, BaseSampleRateMaxHistoryLength);
if (_baseSamplesPerFrame.Count >= BaseSampleRateUsableHistoryLength && !_baseSamplesPerFrame.Any(n => n == 0))
if (_baseSamplesPerFrame.Count >= BaseSampleRateUsableHistoryLength &&
!_baseEmptyFrameCorrectionHistory.Any(n => n))
{
double baseAverageSamplesPerFrame = _baseSamplesPerFrame.Average();
if (baseAverageSamplesPerFrame != 0.0)
@ -192,7 +221,13 @@ namespace BizHawk.Client.EmuHawk
AddSamplesToBuffer(samples, count);
}
private void UpdateHistory<T>(Queue<T> queue, T value, int maxLength)
private static double CalculatePowerMean(IEnumerable<int> values, double power)
{
double x = values.Average(n => Math.Pow(Math.Abs(n), power) * Math.Sign(n));
return Math.Pow(Math.Abs(x), 1.0 / power) * Math.Sign(x);
}
private static void UpdateHistory<T>(Queue<T> queue, T value, int maxLength)
{
queue.Enqueue(value);
while (queue.Count > maxLength)

View File

@ -321,7 +321,11 @@ namespace BizHawk.Client.EmuHawk
if (elapsedTime >= timePerFrame)
{
if (elapsedTime >= timePerFrame * 4)
int maxMissedFrames = (int)Math.Ceiling((Global.SoundMaxBufferDeficitMs / 1000.0) / ((double)timePerFrame / afsfreq));
if (maxMissedFrames < 3)
maxMissedFrames = 3;
if (elapsedTime > timePerFrame * (ulong)(1 + maxMissedFrames))
ltime = ttime;
else
ltime += timePerFrame;