用于读取 NAudio 缓冲区的虚拟输出

Dummy output for reading NAudio buffers

我尝试进行以下设置,当我使用真实输出时它工作正常。

我不确定这样做的正确方法是什么,我尝试使用计时器并且它工作了一段时间,但后来失败了,因为它有点漂移并且我得到了缓冲区已满的异常。

  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();

我已经尝试计算每次报价时我应该读多少,例如

var readCount = Math.Min(inputABufferedWaveProvider.BufferedBytes, inputBBufferedWaveProvider.BufferedBytes);

但无法使其工作,我尝试使用 DataAvailable 事件,但由于有两个输入并且它们是混合的,我也无法工作。

System.Timer.Timer 的分辨率约为 15.6 毫秒,基于 Windows 时钟时间。您需要使用更准确的机制来跟踪时间,并根据真实时间而不是计时器滴答的速率调整您的读取速率。

最流行的跟踪经过时间的方法是使用 System.Diagnostics.Stopwatch 来确定自您的进程开始以来实际经过了多少时间,然后您可以使用它来计算要保留的样本数同步。

这是一个 IWaveOutput 实现,它使用计时器和秒表来确定要从其输入中读取多少样本:

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;}
}

我用这个连接到 BufferedWaveProvider 进行了一些测试,该 BufferedWaveProvider 从默认 WaveInEvent 实例(8kHz PCM 16 位单声道)提供样本。根据总 运行 时间与读取次数的对比判断,计时器在 93 毫秒左右滴答作响,而不是请求的 90 毫秒,并且输入缓冲区的长度始终保持在 3800 字节以下。更改为 44.1kHz 立体声 IeeeFloat 格式将缓冲区大小增加到略低于 80kB……仍然非常易于管理,并且没有溢出。在这两种情况下,数据到达的块都不到最大缓冲区大小的一半 - 每个 DataAvailable 事件 35280 字节,而 60 秒 运行 内的最大缓冲区长度为 76968 字节,DataAvailable 每次触发平均 100 毫秒。

试一试,看看效果如何。