I trying to do the following setup and it works fine when I use a real output.
I´m not sure what the right approach is to do that, I tried to use a Timer and it works for some time, but then fails because it drifts a bit and I get a buffer full exception.
var mixSampleProvider = new MixingSampleProvider(resampleWaveFormat);
mixSampleProvider.AddMixerInput(inputAResampler);
mixSampleProvider.AddMixerInput(inputBResampler);
var mixWaveProvider = new SampleToWaveProvider(mixSampleProvider);
savingWaveProvider = new SavingWaveProvider(mixWaveProvider);
System.Timers.Timer timer = new System.Timers.Timer(98);
timer.Elapsed += (sender, args) =>
{
var count = resampleWaveFormat.AverageBytesPerSecond / 10;
var dummy = new byte[count];
savingWaveProvider.Read(dummy, 0, count);
};
timer.Start();
I have tried to calculate how much I should read on each tick e.g.
var readCount = Math.Min(inputABufferedWaveProvider.BufferedBytes, inputBBufferedWaveProvider.BufferedBytes);
but cannot make it work, and I have tried to use the DataAvailable event, but since there are two input and they are mixed I cannot that to work either.
The resolution of System.Timer.Timer
is approximately 15.6ms, based on the Windows clock time. You need to track the time using a more accurate mechanism and adjust your read rates based on the true time rather than the rate of timer ticks.
The most popular method of tracking elapsed time is to use a System.Diagnostics.Stopwatch
to determine how much time has actually elapsed since your process started, which you can then use to calculate the number of samples to read to stay in sync.
Here's a IWaveOutput
implementation that uses a timer and a stopwatch to figure out how many samples to read from its input:
public class SyncedNullOutput : IWavePlayer
{
// where to read data from
private IWaveProvider _source;
// time measurement
Stopwatch _stopwatch = null;
double _lastTime = 0;
// timer to fire our read method
System.Timers.Timer _timer = null;
PlaybackState _state = PlaybackState.Stopped;
public PlaybackState PlaybackState { get { return _state; } }
public SuncedNullOutput()
{ }
public SyncedNullOutput(IWaveProvider source)
{
Init(source);
}
public void Dispose()
{
Stop();
}
void _timer_Elapsed(object sender, ElapsedEventArgs args)
{
// get total elapsed time, compare to last time
double elapsed = _stopwatch.Elapsed.TotalSeconds;
double deltaTime = elapsed - _lastTime;
_lastTime = elapsed;
// work out number of samples we need to read...
int nSamples = (int)(deltaTime * _source.WaveFormat.SampleRate);
// ...and how many bytes those samples occupy
int nBytes = nSamples * _source.WaveFormat.BlockAlign;
// Read samples from the source
byte[] buffer = new byte[nBytes];
_source.Read(buffer, 0, nBytes);
}
public void Play()
{
if (_state == PlaybackState.Stopped)
{
// create timer
_timer = new System.Timers.Timer(90);
_timer.AutoReset = true;
_timer.Elapsed += _timer_Elapsed;
_timer.Start();
// create stopwatch
_stopwatch = Stopwatch.StartNew();
_lastTime = 0;
}
else if (_state == PlaybackState.Paused)
{
// reset stopwatch
_stopwatch.Reset();
_lastTime = 0;
// restart timer
_timer.Start();
}
_state = PlaybackState.Playing;
}
public void Stop()
{
if (_timer != null)
{
_timer.Stop();
_timer.Dispose();
_timer = null;
}
if (_stopwatch != null)
{
_stopwatch.Stop();
_stopwatch = null;
}
_lastTime = 0;
_state = PlaybackState.Stopped;
}
public void Pause()
{
_timer.Stop();
_state = PlaybackState.Paused;
}
public void Init(IWaveProvider waveProvider)
{
Stop();
_source = waveProvider;
}
public event EventHandler<StoppedEventArgs> PlaybackStopped;
protected void OnPlaybackStopped(Exception exception = null)
{
if (PlaybackStopped != null)
PlaybackStopped(this, new StoppedEventArgs(exception));
}
public float Volume {get;set;}
}
I did some tests with this hooked up to a BufferedWaveProvider
that was being fed samples from a default WaveInEvent
instance (8kHz PCM 16-bit mono). The timer was ticking at around 93ms instead of the requested 90ms, as judged by the total run time vs number of reads, and the input buffer remained constantly under 3800 bytes in length. Changing to 44.1kHz stereo IeeeFloat format upped the buffer size to just under 80kB... still very manageable, and no overflows. In both cases the data was arriving in blocks just under half the maximum buffer size - 35280 bytes per DataAvailable
event vs 76968 bytes maximum buffer length in a 60 second run, with DataAvailable
firing every 100ms on average.
Try it out and see how well it works for you.