SoundOutputProvider: Proper fix for cores with weird audio timing (e.g. N64).

This commit is contained in:
jdpurcell 2015-01-24 03:49:22 +00:00
parent f89f8fc457
commit 219d10fd7c
2 changed files with 36 additions and 38 deletions

View File

@ -47,7 +47,7 @@ namespace BizHawk.Client.EmuHawk
private const int BufferSizeSamples = SampleRate * BufferSizeMilliseconds / 1000; private const int BufferSizeSamples = SampleRate * BufferSizeMilliseconds / 1000;
private const int BufferSizeBytes = BufferSizeSamples * BlockAlign; private const int BufferSizeBytes = BufferSizeSamples * BlockAlign;
private const double BufferSizeSeconds = (double)(BufferSizeBytes / BlockAlign) / SampleRate; private const double BufferSizeSeconds = (double)(BufferSizeBytes / BlockAlign) / SampleRate;
private const int MinBufferFullnessMilliseconds = 60; private const int MinBufferFullnessMilliseconds = 55;
private const int MinBufferFullnessSamples = SampleRate * MinBufferFullnessMilliseconds / 1000; private const int MinBufferFullnessSamples = SampleRate * MinBufferFullnessMilliseconds / 1000;
private bool _muted; private bool _muted;

View File

@ -21,7 +21,7 @@ namespace BizHawk.Client.EmuHawk
{ {
private const int SampleRate = 44100; private const int SampleRate = 44100;
private const int ChannelCount = 2; private const int ChannelCount = 2;
private const int MaxExtraMilliseconds = 40; private const int MaxExtraMilliseconds = 45;
private const int MaxExtraSamples = SampleRate * MaxExtraMilliseconds / 1000; private const int MaxExtraSamples = SampleRate * MaxExtraMilliseconds / 1000;
private const int MaxTargetOffsetMilliseconds = 5; private const int MaxTargetOffsetMilliseconds = 5;
private const int MaxTargetOffsetSamples = SampleRate * MaxTargetOffsetMilliseconds / 1000; private const int MaxTargetOffsetSamples = SampleRate * MaxTargetOffsetMilliseconds / 1000;
@ -30,6 +30,9 @@ namespace BizHawk.Client.EmuHawk
private const int UsableHistoryLength = 20; private const int UsableHistoryLength = 20;
private const int MaxHistoryLength = 60; private const int MaxHistoryLength = 60;
private const int SoftCorrectionLength = 240; private const int SoftCorrectionLength = 240;
private const int BaseSampleRateUsableHistoryLength = 60;
private const int BaseSampleRateMaxHistoryLength = 300;
private const int MinResamplingDistanceSamples = 3;
private Queue<short> _buffer = new Queue<short>(MaxExtraSamples * ChannelCount); private Queue<short> _buffer = new Queue<short>(MaxExtraSamples * ChannelCount);
@ -37,9 +40,8 @@ namespace BizHawk.Client.EmuHawk
private Queue<int> _outputCountHistory = new Queue<int>(); private Queue<int> _outputCountHistory = new Queue<int>();
private Queue<bool> _hardCorrectionHistory = new Queue<bool>(); private Queue<bool> _hardCorrectionHistory = new Queue<bool>();
private bool _disableFramerateCompensation; private double _lastAdvertisedSamplesPerFrame;
private double _lastSamplesPerFrame; private Queue<int> _baseSamplesPerFrame = new Queue<int>();
private int _lastBaseProviderSampleCount;
private short[] _resampleBuffer = new short[0]; private short[] _resampleBuffer = new short[0];
private double _resampleLengthRoundingError; private double _resampleLengthRoundingError;
@ -56,9 +58,8 @@ namespace BizHawk.Client.EmuHawk
_extraCountHistory.Clear(); _extraCountHistory.Clear();
_outputCountHistory.Clear(); _outputCountHistory.Clear();
_hardCorrectionHistory.Clear(); _hardCorrectionHistory.Clear();
_disableFramerateCompensation = false; _lastAdvertisedSamplesPerFrame = 0.0;
_lastSamplesPerFrame = 0.0; _baseSamplesPerFrame.Clear();
_lastBaseProviderSampleCount = 0;
_resampleBuffer = new short[0]; _resampleBuffer = new short[0];
_resampleLengthRoundingError = 0.0; _resampleLengthRoundingError = 0.0;
@ -70,7 +71,7 @@ namespace BizHawk.Client.EmuHawk
public bool LogDebug { get; set; } public bool LogDebug { get; set; }
private double SamplesPerFrame private double AdvertisedSamplesPerFrame
{ {
get { return SampleRate / Global.Emulator.CoreComm.VsyncRate; } get { return SampleRate / Global.Emulator.CoreComm.VsyncRate; }
} }
@ -89,7 +90,7 @@ namespace BizHawk.Client.EmuHawk
} }
} }
GetSamplesFromBase(scaleFactor); GetSamplesFromBase(ref scaleFactor);
int bufferSampleCount = _buffer.Count / ChannelCount; int bufferSampleCount = _buffer.Count / ChannelCount;
int extraSampleCount = bufferSampleCount - idealSampleCount; int extraSampleCount = bufferSampleCount - idealSampleCount;
@ -121,61 +122,58 @@ namespace BizHawk.Client.EmuHawk
int outputSampleCount = Math.Min(idealSampleCount, bufferSampleCount); int outputSampleCount = Math.Min(idealSampleCount, bufferSampleCount);
UpdateHistory(_extraCountHistory, extraSampleCount); UpdateHistory(_extraCountHistory, extraSampleCount, MaxHistoryLength);
UpdateHistory(_outputCountHistory, outputSampleCount); UpdateHistory(_outputCountHistory, outputSampleCount, MaxHistoryLength);
UpdateHistory(_hardCorrectionHistory, hardCorrected); UpdateHistory(_hardCorrectionHistory, hardCorrected, MaxHistoryLength);
GetSamplesFromBuffer(samples, outputSampleCount); GetSamplesFromBuffer(samples, outputSampleCount);
if (LogDebug) if (LogDebug)
{ {
Console.WriteLine("Avg: {0:0.0} ms, Min: {1:0.0} ms, Max: {2:0.0} ms, Scale: {3:0.0000} {4}", 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, _extraCountHistory.Average() * 1000.0 / SampleRate,
_extraCountHistory.Min() * 1000.0 / SampleRate, _extraCountHistory.Min() * 1000.0 / SampleRate,
_extraCountHistory.Max() * 1000.0 / SampleRate, _extraCountHistory.Max() * 1000.0 / SampleRate,
scaleFactor, scaleFactor);
_disableFramerateCompensation ? "*" : "");
} }
return outputSampleCount; return outputSampleCount;
} }
private void GetSamplesFromBase(double scaleFactor) private void GetSamplesFromBase(ref double scaleFactor)
{ {
short[] samples; short[] samples;
int count; int count;
BaseSoundProvider.GetSamples(out samples, out count); BaseSoundProvider.GetSamples(out samples, out count);
if (SamplesPerFrame != _lastSamplesPerFrame) if (AdvertisedSamplesPerFrame != _lastAdvertisedSamplesPerFrame)
{ {
_disableFramerateCompensation = false; _baseSamplesPerFrame.Clear();
_lastAdvertisedSamplesPerFrame = AdvertisedSamplesPerFrame;
}
if (count != 0)
{
UpdateHistory(_baseSamplesPerFrame, count, BaseSampleRateMaxHistoryLength);
} }
if (count != 0 && !_disableFramerateCompensation) if (_baseSamplesPerFrame.Count >= BaseSampleRateUsableHistoryLength)
{ {
if (_lastBaseProviderSampleCount != 0 && Math.Abs(count - _lastBaseProviderSampleCount) > 10) double baseAverageSamplesPerFrame = _baseSamplesPerFrame.Average();
if (baseAverageSamplesPerFrame != 0.0)
{ {
_disableFramerateCompensation = true; scaleFactor *= AdvertisedSamplesPerFrame / baseAverageSamplesPerFrame;
} }
scaleFactor *= SamplesPerFrame / count;
_lastBaseProviderSampleCount = count;
} }
_lastSamplesPerFrame = SamplesPerFrame;
double newCountTarget = count * scaleFactor; double newCountTarget = count * scaleFactor;
int newCount = (int)Math.Round(newCountTarget + _resampleLengthRoundingError); int newCount = (int)Math.Round(newCountTarget + _resampleLengthRoundingError);
// Do not resample for one-sample differences. With NTSC @ 59.94 FPS, for example, // Due to small inaccuracies and rounding errors, it's pointless to resample by
// there are ~735.7 samples per frame so the source will oscillate between 735 and // just a sample or two because those may be fluctuations that will average out
// 736 samples. Our calculated number of samples will also oscillate between 735 // over time. So instead of immediately resampling to cover small differences, we
// and 736, but likely out of phase. There's no point resampling to make up for // will just keep track of it as part of the rounding error and only resample later
// something that will average out over time, so don't resample for these // if a more significant difference accumulates.
// differences. We will, however, keep track of them as part of the rounding error if (Math.Abs(newCount - count) >= MinResamplingDistanceSamples)
// in case they end up not averaging out as expected.
if (Math.Abs(newCount - count) > 1)
{ {
samples = Resample(samples, count, newCount); samples = Resample(samples, count, newCount);
count = newCount; count = newCount;
@ -189,10 +187,10 @@ namespace BizHawk.Client.EmuHawk
AddSamplesToBuffer(samples, count); AddSamplesToBuffer(samples, count);
} }
private void UpdateHistory<T>(Queue<T> queue, T value) private void UpdateHistory<T>(Queue<T> queue, T value, int maxLength)
{ {
queue.Enqueue(value); queue.Enqueue(value);
while (queue.Count > MaxHistoryLength) while (queue.Count > maxLength)
{ {
queue.Dequeue(); queue.Dequeue();
} }