Sound: Improve audio throttle behavior with N64. GetSamplesSync sometimes returns > 100ms of audio from N64.

This commit is contained in:
J.D. Purcell 2019-10-13 22:56:14 -04:00
parent 6504387302
commit c42bf37e86
6 changed files with 53 additions and 30 deletions

View File

@ -9,6 +9,6 @@ namespace BizHawk.Client.EmuHawk
void ApplyVolumeSettings(double volume); void ApplyVolumeSettings(double volume);
int MaxSamplesDeficit { get; } int MaxSamplesDeficit { get; }
int CalculateSamplesNeeded(); int CalculateSamplesNeeded();
void WriteSamples(short[] samples, int sampleCount); void WriteSamples(short[] samples, int sampleOffset, int sampleCount);
} }
} }

View File

@ -152,12 +152,10 @@ namespace BizHawk.Client.EmuHawk
return (end - start + size) % size; return (end - start + size) % size;
} }
public void WriteSamples(short[] samples, int sampleCount) public void WriteSamples(short[] samples, int sampleOffset, int sampleCount)
{ {
if (sampleCount == 0) return; if (sampleCount == 0) return;
int total = sampleCount * Sound.ChannelCount; _deviceBuffer.Write(samples, sampleOffset * Sound.ChannelCount, sampleCount * Sound.ChannelCount, _actualWriteOffsetBytes, LockFlags.None);
if (total > samples.Length) { total = samples.Length; }
_deviceBuffer.Write(samples, 0, total, _actualWriteOffsetBytes, LockFlags.None);
_actualWriteOffsetBytes = (_actualWriteOffsetBytes + (sampleCount * Sound.BlockAlign)) % BufferSizeBytes; _actualWriteOffsetBytes = (_actualWriteOffsetBytes + (sampleCount * Sound.BlockAlign)) % BufferSizeBytes;
_filledBufferSizeBytes += sampleCount * Sound.BlockAlign; _filledBufferSizeBytes += sampleCount * Sound.BlockAlign;
} }

View File

@ -73,7 +73,7 @@ namespace BizHawk.Client.EmuHawk
return samplesNeeded; return samplesNeeded;
} }
public void WriteSamples(short[] samples, int sampleCount) public void WriteSamples(short[] samples, int sampleOffset, int sampleCount)
{ {
if (sampleCount == 0) return; if (sampleCount == 0) return;
_remainingSamples += sampleCount; _remainingSamples += sampleCount;

View File

@ -17,6 +17,7 @@ namespace BizHawk.Client.EmuHawk
private int _sourceID; private int _sourceID;
private BufferPool _bufferPool; private BufferPool _bufferPool;
private int _currentSamplesQueued; private int _currentSamplesQueued;
private short[] _tempSampleBuffer;
public OpenALSoundOutput(Sound sound) public OpenALSoundOutput(Sound sound)
{ {
@ -94,11 +95,17 @@ namespace BizHawk.Client.EmuHawk
return samplesNeeded; return samplesNeeded;
} }
public void WriteSamples(short[] samples, int sampleCount) public void WriteSamples(short[] samples, int sampleOffset, int sampleCount)
{ {
if (sampleCount == 0) return; if (sampleCount == 0) return;
UnqueueProcessedBuffers(); UnqueueProcessedBuffers();
int byteCount = sampleCount * Sound.BlockAlign; int byteCount = sampleCount * Sound.BlockAlign;
if (sampleOffset != 0)
{
AllocateTempSampleBuffer(sampleCount);
Buffer.BlockCopy(samples, sampleOffset * Sound.BlockAlign, _tempSampleBuffer, 0, byteCount);
samples = _tempSampleBuffer;
}
var buffer = _bufferPool.Obtain(byteCount); var buffer = _bufferPool.Obtain(byteCount);
AL.BufferData(buffer.BufferID, ALFormat.Stereo16, samples, byteCount, Sound.SampleRate); AL.BufferData(buffer.BufferID, ALFormat.Stereo16, samples, byteCount, Sound.SampleRate);
AL.SourceQueueBuffer(_sourceID, buffer.BufferID); AL.SourceQueueBuffer(_sourceID, buffer.BufferID);
@ -127,6 +134,15 @@ namespace BizHawk.Client.EmuHawk
return value; return value;
} }
private void AllocateTempSampleBuffer(int sampleCount)
{
int length = sampleCount * Sound.ChannelCount;
if (_tempSampleBuffer == null || _tempSampleBuffer.Length < length)
{
_tempSampleBuffer = new short[length];
}
}
private class BufferPool : IDisposable private class BufferPool : IDisposable
{ {
private Stack<BufferPoolItem> _availableItems = new Stack<BufferPoolItem>(); private Stack<BufferPoolItem> _availableItems = new Stack<BufferPoolItem>();

View File

@ -112,14 +112,13 @@ namespace BizHawk.Client.EmuHawk
return samplesNeeded; return samplesNeeded;
} }
public void WriteSamples(short[] samples, int sampleCount) public void WriteSamples(short[] samples, int sampleOffset, int sampleCount)
{ {
if (sampleCount == 0) return; if (sampleCount == 0) return;
_bufferPool.Release(_sourceVoice.State.BuffersQueued); _bufferPool.Release(_sourceVoice.State.BuffersQueued);
int byteCount = sampleCount * Sound.BlockAlign; int byteCount = sampleCount * Sound.BlockAlign;
var buffer = _bufferPool.Obtain(byteCount); var buffer = _bufferPool.Obtain(byteCount);
if (byteCount > (samples.Length * 2)) { byteCount = samples.Length * 2; } Buffer.BlockCopy(samples, sampleOffset * Sound.BlockAlign, buffer.Bytes, 0, byteCount);
Buffer.BlockCopy(samples, 0, buffer.Bytes, 0, byteCount);
_sourceVoice.SubmitSourceBuffer(new AudioBuffer _sourceVoice.SubmitSourceBuffer(new AudioBuffer
{ {
AudioBytes = byteCount, AudioBytes = byteCount,

View File

@ -16,8 +16,6 @@ namespace BizHawk.Client.EmuHawk
public const int BlockAlign = BytesPerSample * ChannelCount; public const int BlockAlign = BytesPerSample * ChannelCount;
private bool _disposed; private bool _disposed;
private bool _unjamSoundThrottle;
private readonly ISoundOutput _outputDevice; private readonly ISoundOutput _outputDevice;
private readonly SoundOutputProvider _outputProvider = new SoundOutputProvider(); // Buffer for Sync sources private readonly SoundOutputProvider _outputProvider = new SoundOutputProvider(); // Buffer for Sync sources
private readonly BufferedAsync _bufferedAsync = new BufferedAsync(); // Buffer for Async sources private readonly BufferedAsync _bufferedAsync = new BufferedAsync(); // Buffer for Async sources
@ -118,9 +116,8 @@ namespace BizHawk.Client.EmuHawk
// Fill device buffer with silence but leave enough room for one frame // Fill device buffer with silence but leave enough room for one frame
int samplesPerFrame = (int)Math.Round(SampleRate / (double)Global.Emulator.VsyncRate()); int samplesPerFrame = (int)Math.Round(SampleRate / (double)Global.Emulator.VsyncRate());
int silenceSamples = Math.Max(samplesNeeded - samplesPerFrame, 0); int silenceSamples = Math.Max(samplesNeeded - samplesPerFrame, 0);
_outputDevice.WriteSamples(new short[silenceSamples * 2], silenceSamples); _outputDevice.WriteSamples(new short[silenceSamples * 2], 0, silenceSamples);
samplesNeeded -= silenceSamples; samplesNeeded -= silenceSamples;
_unjamSoundThrottle = isUnderrun;
if (isUnderrun) if (isUnderrun)
{ {
@ -141,14 +138,16 @@ namespace BizHawk.Client.EmuHawk
if (atten > 1) atten = 1; if (atten > 1) atten = 1;
_outputDevice.ApplyVolumeSettings(atten); _outputDevice.ApplyVolumeSettings(atten);
short[] samples;
int samplesNeeded = _outputDevice.CalculateSamplesNeeded(); int samplesNeeded = _outputDevice.CalculateSamplesNeeded();
int samplesProvided; short[] samples;
int sampleOffset;
int sampleCount;
if (atten == 0) if (atten == 0)
{ {
samples = new short[samplesNeeded * ChannelCount]; samples = new short[samplesNeeded * ChannelCount];
samplesProvided = samplesNeeded; sampleOffset = 0;
sampleCount = samplesNeeded;
_bufferedProvider.DiscardSamples(); _bufferedProvider.DiscardSamples();
} }
@ -156,25 +155,34 @@ namespace BizHawk.Client.EmuHawk
{ {
if (Global.Config.SoundThrottle) if (Global.Config.SoundThrottle)
{ {
_outputProvider.BaseSoundProvider.GetSamplesSync(out samples, out samplesProvided); _outputProvider.BaseSoundProvider.GetSamplesSync(out samples, out sampleCount);
sampleOffset = 0;
if (Global.DisableSecondaryThrottling && samplesProvided > samplesNeeded) if (Global.DisableSecondaryThrottling && sampleCount > samplesNeeded)
{ {
return; return;
} }
while (samplesProvided > samplesNeeded) int samplesPerMs = SampleRate / 1000;
int outputThresholdMs = 20;
while (sampleCount > samplesNeeded)
{ {
Thread.Sleep((samplesProvided - samplesNeeded) / (SampleRate / 1000)); // Let the audio clock control sleep time if (samplesNeeded >= outputThresholdMs * samplesPerMs)
samplesNeeded = _outputDevice.CalculateSamplesNeeded();
if (_unjamSoundThrottle)
{ {
//may be garbage, but what can we do? // If we were given a large enough number of samples (e.g. larger than the device's
samplesProvided = samplesNeeded; // buffer), the device will never need that many samples no matter how long we
break; // wait, so we have to start splitting up the output
_outputDevice.WriteSamples(samples, sampleOffset, samplesNeeded);
sampleOffset += samplesNeeded;
sampleCount -= samplesNeeded;
} }
else
{
// Let the audio clock control sleep time
Thread.Sleep(Math.Min((sampleCount - samplesNeeded) / samplesPerMs, outputThresholdMs));
}
samplesNeeded = _outputDevice.CalculateSamplesNeeded();
} }
_unjamSoundThrottle = false;
} }
else else
{ {
@ -182,7 +190,8 @@ namespace BizHawk.Client.EmuHawk
{ {
_outputProvider.OnVolatility(); _outputProvider.OnVolatility();
} }
_outputProvider.GetSamples(samplesNeeded, out samples, out samplesProvided); _outputProvider.GetSamples(samplesNeeded, out samples, out sampleCount);
sampleOffset = 0;
} }
} }
else if (_bufferedProvider == _bufferedAsync) else if (_bufferedProvider == _bufferedAsync)
@ -191,14 +200,15 @@ namespace BizHawk.Client.EmuHawk
_bufferedAsync.GetSamplesAsync(samples); _bufferedAsync.GetSamplesAsync(samples);
samplesProvided = samplesNeeded; sampleOffset = 0;
sampleCount = samplesNeeded;
} }
else else
{ {
return; return;
} }
_outputDevice.WriteSamples(samples, samplesProvided); _outputDevice.WriteSamples(samples, sampleOffset, sampleCount);
} }
public static int MillisecondsToSamples(int milliseconds) public static int MillisecondsToSamples(int milliseconds)