From 58cd9796f2994e4f1bbe108f7f47b6c8c2ec1f9f Mon Sep 17 00:00:00 2001 From: jdpurcell Date: Wed, 28 Jan 2015 00:53:49 +0000 Subject: [PATCH] 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. --- BizHawk.Client.Common/Global.cs | 5 ++ BizHawk.Client.EmuHawk/Sound.cs | 12 +++- BizHawk.Client.EmuHawk/SoundOutputProvider.cs | 67 ++++++++++++++----- BizHawk.Client.EmuHawk/Throttle.cs | 6 +- 4 files changed, 70 insertions(+), 20 deletions(-) diff --git a/BizHawk.Client.Common/Global.cs b/BizHawk.Client.Common/Global.cs index fba235b924..70a638b9f7 100644 --- a/BizHawk.Client.Common/Global.cs +++ b/BizHawk.Client.Common/Global.cs @@ -22,6 +22,11 @@ namespace BizHawk.Client.Common /// public static bool DisableSecondaryThrottling; + /// + /// How far the sound output buffer can go below full before drastic corrective measures are taken. + /// + public static int SoundMaxBufferDeficitMs; + public static Controller NullControls; public static AutofireController AutofireNullControls; diff --git a/BizHawk.Client.EmuHawk/Sound.cs b/BizHawk.Client.EmuHawk/Sound.cs index f7c2e00d3a..8412ccd3bc 100644 --- a/BizHawk.Client.EmuHawk/Sound.cs +++ b/BizHawk.Client.EmuHawk/Sound.cs @@ -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; diff --git a/BizHawk.Client.EmuHawk/SoundOutputProvider.cs b/BizHawk.Client.EmuHawk/SoundOutputProvider.cs index fbda4e391d..b4ff27d26b 100644 --- a/BizHawk.Client.EmuHawk/SoundOutputProvider.cs +++ b/BizHawk.Client.EmuHawk/SoundOutputProvider.cs @@ -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 _outputCountHistory = new Queue(); private Queue _hardCorrectionHistory = new Queue(); + private int _baseConsecutiveEmptyFrames; + private Queue _baseEmptyFrameCorrectionHistory = new Queue(); + private double _lastAdvertisedSamplesPerFrame; private Queue _baseSamplesPerFrame = new Queue(); @@ -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(Queue queue, T value, int maxLength) + private static double CalculatePowerMean(IEnumerable 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(Queue queue, T value, int maxLength) { queue.Enqueue(value); while (queue.Count > maxLength) diff --git a/BizHawk.Client.EmuHawk/Throttle.cs b/BizHawk.Client.EmuHawk/Throttle.cs index 89577e8e22..485ddcb330 100644 --- a/BizHawk.Client.EmuHawk/Throttle.cs +++ b/BizHawk.Client.EmuHawk/Throttle.cs @@ -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;