SoundOutputProvider: Standalone mode to eliminate the need for an external buffer when used with SyncToAsyncProvider.

This commit is contained in:
J.D. Purcell 2017-03-25 16:26:46 -04:00
parent a7f5bafb72
commit c58d2929f8
3 changed files with 60 additions and 48 deletions

View File

@ -3142,7 +3142,7 @@ namespace BizHawk.Client.EmuHawk
else else
{ {
_currentSoundProvider.SetSyncMode(SyncSoundMode.Sync); _currentSoundProvider.SetSyncMode(SyncSoundMode.Sync);
_aviSoundInputAsync = new SyncToAsyncProvider(_currentSoundProvider, 10.0); _aviSoundInputAsync = new SyncToAsyncProvider(_currentSoundProvider);
} }
} }
_dumpProxy = new SimpleSyncSoundProvider(); _dumpProxy = new SimpleSyncSoundProvider();

View File

@ -32,6 +32,9 @@ namespace BizHawk.Client.EmuHawk
private const int MinResamplingDistanceSamples = 3; private const int MinResamplingDistanceSamples = 3;
private Queue<short> _buffer = new Queue<short>(); private Queue<short> _buffer = new Queue<short>();
private bool _standaloneMode;
private int _targetExtraSamples;
private int _maxSamplesDeficit;
private Queue<int> _extraCountHistory = new Queue<int>(); private Queue<int> _extraCountHistory = new Queue<int>();
private Queue<int> _outputCountHistory = new Queue<int>(); private Queue<int> _outputCountHistory = new Queue<int>();
@ -48,17 +51,37 @@ namespace BizHawk.Client.EmuHawk
private short[] _resampleBuffer = new short[0]; private short[] _resampleBuffer = new short[0];
private double _resampleLengthRoundingError; private double _resampleLengthRoundingError;
public SoundOutputProvider() public SoundOutputProvider(bool standaloneMode = false)
{ {
_standaloneMode = standaloneMode;
if (_standaloneMode)
{
const double targetExtraMs = 10.0;
_targetExtraSamples = (int)Math.Ceiling(targetExtraMs * SampleRate / 1000.0);
}
ResetBuffer();
} }
public int MaxSamplesDeficit { get; set; } public int MaxSamplesDeficit
{
get { return _maxSamplesDeficit; }
set
{
if (_standaloneMode) throw new InvalidOperationException();
_maxSamplesDeficit = value;
}
}
private int EffectiveMaxSamplesDeficit
{
get { return _maxSamplesDeficit + _targetExtraSamples; }
}
public ISoundProvider BaseSoundProvider { get; set; } public ISoundProvider BaseSoundProvider { get; set; }
public void DiscardSamples() public void DiscardSamples()
{ {
_buffer.Clear(); ResetBuffer();
_extraCountHistory.Clear(); _extraCountHistory.Clear();
_outputCountHistory.Clear(); _outputCountHistory.Clear();
_hardCorrectionHistory.Clear(); _hardCorrectionHistory.Clear();
@ -76,6 +99,15 @@ namespace BizHawk.Client.EmuHawk
} }
} }
private void ResetBuffer()
{
_buffer.Clear();
for (int i = 0; i < _targetExtraSamples * ChannelCount; i++)
{
_buffer.Enqueue(0);
}
}
// To let us know about buffer underruns, rewinding, fast-forwarding, etc. // To let us know about buffer underruns, rewinding, fast-forwarding, etc.
public void OnVolatility() public void OnVolatility()
{ {
@ -91,7 +123,23 @@ namespace BizHawk.Client.EmuHawk
get { return SampleRate / Global.Emulator.CoreComm.VsyncRate; } get { return SampleRate / Global.Emulator.CoreComm.VsyncRate; }
} }
public void GetSamples(short[] samples)
{
if (!_standaloneMode) throw new InvalidOperationException();
int returnSampleCount = samples.Length / ChannelCount;
GetSamples(returnSampleCount);
GetSamplesFromBuffer(samples, returnSampleCount);
}
public void GetSamples(int idealSampleCount, out short[] samples, out int sampleCount) public void GetSamples(int idealSampleCount, out short[] samples, out int sampleCount)
{
if (_standaloneMode) throw new InvalidOperationException();
sampleCount = GetSamples(idealSampleCount);
samples = GetOutputBuffer(sampleCount);
GetSamplesFromBuffer(samples, sampleCount);
}
private int GetSamples(int idealSampleCount)
{ {
double scaleFactor = 1.0; double scaleFactor = 1.0;
@ -108,9 +156,9 @@ namespace BizHawk.Client.EmuHawk
GetSamplesFromBase(ref scaleFactor); GetSamplesFromBase(ref scaleFactor);
int bufferSampleCount = _buffer.Count / ChannelCount; int bufferSampleCount = _buffer.Count / ChannelCount;
int extraSampleCount = bufferSampleCount - idealSampleCount; int extraSampleCount = bufferSampleCount - _targetExtraSamples - idealSampleCount;
int maxSamplesDeficit = _extraCountHistory.Count >= UsableHistoryLength ? int maxSamplesDeficit = _extraCountHistory.Count >= UsableHistoryLength ?
MaxSamplesDeficit : Math.Min(StartupMaxSamplesSurplusDeficit, MaxSamplesDeficit); EffectiveMaxSamplesDeficit : Math.Min(StartupMaxSamplesSurplusDeficit, EffectiveMaxSamplesDeficit);
int maxSamplesSurplus = _extraCountHistory.Count >= UsableHistoryLength ? int maxSamplesSurplus = _extraCountHistory.Count >= UsableHistoryLength ?
MaxSamplesSurplus : Math.Min(StartupMaxSamplesSurplusDeficit, MaxSamplesSurplus); MaxSamplesSurplus : Math.Min(StartupMaxSamplesSurplusDeficit, MaxSamplesSurplus);
bool hardCorrected = false; bool hardCorrected = false;
@ -137,7 +185,7 @@ namespace BizHawk.Client.EmuHawk
} }
bufferSampleCount = _buffer.Count / ChannelCount; bufferSampleCount = _buffer.Count / ChannelCount;
extraSampleCount = bufferSampleCount - idealSampleCount; extraSampleCount = bufferSampleCount - _targetExtraSamples - idealSampleCount;
int outputSampleCount = Math.Min(idealSampleCount, bufferSampleCount); int outputSampleCount = Math.Min(idealSampleCount, bufferSampleCount);
@ -154,9 +202,7 @@ namespace BizHawk.Client.EmuHawk
scaleFactor); scaleFactor);
} }
sampleCount = outputSampleCount; return outputSampleCount;
samples = GetOutputBuffer(sampleCount);
GetSamplesFromBuffer(samples, sampleCount);
} }
private void GetSamplesFromBase(ref double scaleFactor) private void GetSamplesFromBase(ref double scaleFactor)

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
@ -7,41 +6,16 @@ namespace BizHawk.Client.EmuHawk
{ {
public class SyncToAsyncProvider : ISoundProvider public class SyncToAsyncProvider : ISoundProvider
{ {
private const int SampleRate = 44100; private readonly SoundOutputProvider _outputProvider = new SoundOutputProvider(standaloneMode: true);
private const int ChannelCount = 2;
private const double MinExpectedFrameRate = 50;
private readonly SoundOutputProvider _outputProvider = new SoundOutputProvider(); public SyncToAsyncProvider(ISoundProvider baseProvider)
private readonly int _bufferSizeSamples;
private readonly Queue<short> _buffer;
public SyncToAsyncProvider(ISoundProvider baseProvider, double bufferSizeMs)
{ {
_outputProvider.BaseSoundProvider = baseProvider; _outputProvider.BaseSoundProvider = baseProvider;
_bufferSizeSamples = (int)Math.Ceiling(bufferSizeMs * SampleRate / 1000.0);
_buffer = new Queue<short>((_bufferSizeSamples + (int)Math.Ceiling(SampleRate / MinExpectedFrameRate)) * ChannelCount);
DiscardSamples();
} }
public void DiscardSamples() public void DiscardSamples()
{ {
_buffer.Clear();
_outputProvider.DiscardSamples(); _outputProvider.DiscardSamples();
for (int i = 0; i < _bufferSizeSamples * ChannelCount; i++)
{
_buffer.Enqueue(0);
}
}
public void GetSamplesAsync(short[] samples)
{
GetSamplesFromBase(samples.Length / ChannelCount);
for (int i = 0; i < samples.Length; i++)
{
samples[i] = _buffer.Dequeue();
}
} }
public bool CanProvideAsync public bool CanProvideAsync
@ -67,17 +41,9 @@ namespace BizHawk.Client.EmuHawk
throw new InvalidOperationException("Sync mode is not supported."); throw new InvalidOperationException("Sync mode is not supported.");
} }
private void GetSamplesFromBase(int minResultingSampleCount) public void GetSamplesAsync(short[] samples)
{ {
int idealSampleCount = Math.Max(_bufferSizeSamples + minResultingSampleCount - (_buffer.Count / ChannelCount), 0); _outputProvider.GetSamples(samples);
short[] samples;
int samplesProvided;
_outputProvider.MaxSamplesDeficit = _bufferSizeSamples;
_outputProvider.GetSamples(idealSampleCount, out samples, out samplesProvided);
for (int i = 0; i < samplesProvided * ChannelCount; i++)
{
_buffer.Enqueue(samples[i]);
}
} }
} }
} }