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);
int MaxSamplesDeficit { get; }
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;
}
public void WriteSamples(short[] samples, int sampleCount)
public void WriteSamples(short[] samples, int sampleOffset, int sampleCount)
{
if (sampleCount == 0) return;
int total = sampleCount * Sound.ChannelCount;
if (total > samples.Length) { total = samples.Length; }
_deviceBuffer.Write(samples, 0, total, _actualWriteOffsetBytes, LockFlags.None);
_deviceBuffer.Write(samples, sampleOffset * Sound.ChannelCount, sampleCount * Sound.ChannelCount, _actualWriteOffsetBytes, LockFlags.None);
_actualWriteOffsetBytes = (_actualWriteOffsetBytes + (sampleCount * Sound.BlockAlign)) % BufferSizeBytes;
_filledBufferSizeBytes += sampleCount * Sound.BlockAlign;
}

View File

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

View File

@ -17,6 +17,7 @@ namespace BizHawk.Client.EmuHawk
private int _sourceID;
private BufferPool _bufferPool;
private int _currentSamplesQueued;
private short[] _tempSampleBuffer;
public OpenALSoundOutput(Sound sound)
{
@ -94,11 +95,17 @@ namespace BizHawk.Client.EmuHawk
return samplesNeeded;
}
public void WriteSamples(short[] samples, int sampleCount)
public void WriteSamples(short[] samples, int sampleOffset, int sampleCount)
{
if (sampleCount == 0) return;
UnqueueProcessedBuffers();
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);
AL.BufferData(buffer.BufferID, ALFormat.Stereo16, samples, byteCount, Sound.SampleRate);
AL.SourceQueueBuffer(_sourceID, buffer.BufferID);
@ -127,6 +134,15 @@ namespace BizHawk.Client.EmuHawk
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 Stack<BufferPoolItem> _availableItems = new Stack<BufferPoolItem>();

View File

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

View File

@ -16,8 +16,6 @@ namespace BizHawk.Client.EmuHawk
public const int BlockAlign = BytesPerSample * ChannelCount;
private bool _disposed;
private bool _unjamSoundThrottle;
private readonly ISoundOutput _outputDevice;
private readonly SoundOutputProvider _outputProvider = new SoundOutputProvider(); // Buffer for Sync 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
int samplesPerFrame = (int)Math.Round(SampleRate / (double)Global.Emulator.VsyncRate());
int silenceSamples = Math.Max(samplesNeeded - samplesPerFrame, 0);
_outputDevice.WriteSamples(new short[silenceSamples * 2], silenceSamples);
_outputDevice.WriteSamples(new short[silenceSamples * 2], 0, silenceSamples);
samplesNeeded -= silenceSamples;
_unjamSoundThrottle = isUnderrun;
if (isUnderrun)
{
@ -141,14 +138,16 @@ namespace BizHawk.Client.EmuHawk
if (atten > 1) atten = 1;
_outputDevice.ApplyVolumeSettings(atten);
short[] samples;
int samplesNeeded = _outputDevice.CalculateSamplesNeeded();
int samplesProvided;
short[] samples;
int sampleOffset;
int sampleCount;
if (atten == 0)
{
samples = new short[samplesNeeded * ChannelCount];
samplesProvided = samplesNeeded;
sampleOffset = 0;
sampleCount = samplesNeeded;
_bufferedProvider.DiscardSamples();
}
@ -156,25 +155,34 @@ namespace BizHawk.Client.EmuHawk
{
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;
}
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
samplesNeeded = _outputDevice.CalculateSamplesNeeded();
if (_unjamSoundThrottle)
if (samplesNeeded >= outputThresholdMs * samplesPerMs)
{
//may be garbage, but what can we do?
samplesProvided = samplesNeeded;
break;
// If we were given a large enough number of samples (e.g. larger than the device's
// buffer), the device will never need that many samples no matter how long we
// 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
{
@ -182,7 +190,8 @@ namespace BizHawk.Client.EmuHawk
{
_outputProvider.OnVolatility();
}
_outputProvider.GetSamples(samplesNeeded, out samples, out samplesProvided);
_outputProvider.GetSamples(samplesNeeded, out samples, out sampleCount);
sampleOffset = 0;
}
}
else if (_bufferedProvider == _bufferedAsync)
@ -191,14 +200,15 @@ namespace BizHawk.Client.EmuHawk
_bufferedAsync.GetSamplesAsync(samples);
samplesProvided = samplesNeeded;
sampleOffset = 0;
sampleCount = samplesNeeded;
}
else
{
return;
}
_outputDevice.WriteSamples(samples, samplesProvided);
_outputDevice.WriteSamples(samples, sampleOffset, sampleCount);
}
public static int MillisecondsToSamples(int milliseconds)