NAudio:使用 ASIO 录制音频并用吉他输出

Posted

技术标签:

【中文标题】NAudio:使用 ASIO 录制音频并用吉他输出【英文标题】:NAudio: Using ASIO to record audio and output with a guitar 【发布时间】:2014-03-20 19:44:32 【问题描述】:

我目前正在处理 SR。设计项目是一个 Windows 窗体应用程序,它允许用户插入他们的吉他并实时创建失真,并通过输入和输出播放它,并自动将其标记为 ASCII 选项卡。目前我正在尝试让实时收听部分正常工作,我的失真录音和实现工作得很好,只是在使用 ASIO 时遇到了一些问题。我看过这篇帖子How to record and playback with NAudio using AsioOut,但这对我的问题没有多大帮助,这是我的代码:

private BufferedWaveProvider buffer;
private AsioOut input;
private AsioOut output;

private void listenBtn_Click(object sender, EventArgs e)

    input = new AsioOut(RecordInCbox.SelectedIndex);
    WaveFormat format = new WaveFormat();
    buffer = new BufferedWaveProvider(format);
    buffer.DiscardOnBufferOverflow = true;
    input.InitRecordAndPlayback(buffer, 1, 44100);
    input.AudioAvailable += new EventHandler<AsioAudioAvailableEventArgs>(AudioAvailable);

    //output = new AsioOut(RecordInCbox.SelectedIndex);
    //output.Init(buffer);

    input.Play();
    //output.Play();


public void AudioAvailable(object sender, AsioAudioAvailableEventArgs e)

    byte[] buf = new byte[e.SamplesPerBuffer];
    e.WrittenToOutputBuffers = true;
    for (int i = 0; i < e.InputBuffers.Length; i++)
    
        Array.Copy(e.InputBuffers, e.OutputBuffers, 1);
        Marshal.Copy(e.InputBuffers[i], buf, 0, e.SamplesPerBuffer);
        buffer.AddSamples(buf, 0, buf.Length);
    

目前它正在获取音频并将其推入缓冲区,但输出不起作用。如果我将录音设置设置为在 windows 上听,我可以让它弹吉他,但我觉得这是不必要的,因为我希望能够执行我的失真并将其作为输出听到。谢谢!

【问题讨论】:

【参考方案1】:

您不需要在缓冲区中添加样本。缓冲区仅用于确定您想要的输出通道数。 我是这样做的:

[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
private static unsafe extern void MoveMemory(IntPtr dest, IntPtr src, int size);

private void OnAudioAvailable(object sender, AsioAudioAvailableEventArgs e)
    
        for (int i = 0; i < e.InputBuffers.Length; i++)
        
            MoveMemory(e.OutputBuffers[i], e.InputBuffers[i], e.SamplesPerBuffer * e.InputBuffers.Length);
        
        e.WrittenToOutputBuffers = true;
    

但是这样做感觉有点延迟和一点回声,我不知道如何解决它们。因此,如果您有任何想法,我会在这里倾听。

【讨论】:

【参考方案2】:

不幸的是,我找不到让 ASIO 工作的方法,但我想出了一个同样有效的替代方法,至于延迟我把它降低到 50 毫秒,但一直在研究NAudio source 看看是否有办法让它低于这个值。 (大约 20-30 毫秒)为了更好的实时播放。

    private BufferedWaveProvider buffer;
    private WaveOut waveOut;
    private WaveIn sourceStream = null;

    private bool listen = false;

    private void listenBtn_Click(object sender, EventArgs e)
    
        listen = !listen;
        if (listen)
            listenBtn.Text = "Stop listening";
        else
        
            listenBtn.Text = "Listen";
            sourceStream.StopRecording();
            return;
        

        sourceStream = new WaveIn();
        sourceStream.WaveFormat = new WaveFormat(44100, 1);

        waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback());

        sourceStream.DataAvailable += new EventHandler<WaveInEventArgs>(sourceStream_DataAvailable);
        sourceStream.RecordingStopped += new EventHandler<StoppedEventArgs>(sourceStream_RecordingStopped);

        buffer = new BufferedWaveProvider(sourceStream.WaveFormat);
        buffer.DiscardOnBufferOverflow = true;
        waveOut.DesiredLatency = 51;
        waveOut.Volume = 1f;
        waveOut.Init(buffer);
        sourceStream.StartRecording();
    

    private void sourceStream_DataAvailable(object sender, WaveInEventArgs e)
    
        buffer.AddSamples(e.Buffer, 0, e.BytesRecorded);

        waveOut.Play();
    

    private void sourceStream_RecordingStopped(object sender, StoppedEventArgs e)
    
        sourceStream.Dispose();
        waveOut.Dispose();
    

我再次明白这不是使用 ASIO,但根据我可用的资源和文档,它是一个更好的选择。而不是使用 ASIO,我只是创建 waveIn 并模拟“录音”,而不是将其写入文件,而是将流推送到 waveOut 缓冲区中,这将允许它在我进行一些声音操作后播放。

【讨论】:

【参考方案3】:

可能我错了,但我已经成功地使用 NAudio 以非常低的延迟(在非常便宜的 USB 音频硬件上)同时管理 Asio 录制和播放。

您可以尝试以下方法,而不是第一个示例中使用的事件处理程序方法:

    private float[] recordingBuffer = null;
    private byte[] recordingByteBuffer = null;

    private BufferedWaveProvider bufferedWaveProvider;
    private BufferedSampleProvider bsp;
    private SampleToWaveProvider swp;



    // somewhere in e.g. constructor
            // set up our signal chain
            bufferedWaveProvider = new BufferedWaveProvider(waveFormat);
            //bufferedWaveProvider.DiscardOnBufferOverflow = true;

            bsp = new BufferedSampleProvider(waveFormat);
            swp = new SampleToWaveProvider(bsp);
    // ...


    private void OnAudioAvailable(object sender, AsioAudioAvailableEventArgs e)
    
        this.recordingBuffer = BufferHelpers.Ensure(this.recordingBuffer, e.SamplesPerBuffer * e.InputBuffers.Length);
        this.recordingByteBuffer = BufferHelpers.Ensure(this.recordingByteBuffer, e.SamplesPerBuffer  * 4 * e.InputBuffers.Length);

        int count = e.GetAsInterleavedSamples(this.recordingBuffer);

        this.bsp.CurrentBuffer = this.recordingBuffer;

        int count2 = this.swp.Read(this.recordingByteBuffer, 0, count * 4);

        bufferedWaveProvider.AddSamples(this.recordingByteBuffer, 0, this.recordingByteBuffer.Length);
    

使用 BufferedSampleProvider.cs 类:

public class BufferedSampleProvider : ISampleProvider

    private WaveFormat waveFormat;
    private float[] currentBuffer;

    public BufferedSampleProvider(WaveFormat waveFormat)
    
        this.waveFormat = waveFormat;
        this.currentBuffer = null;
    

    public float[] CurrentBuffer 
    
        get  return this.currentBuffer; 
        set  this.currentBuffer = value; 
    

    public int Read(float[] buffer, int offset, int count)
    
        if (this.currentBuffer != null)
        
            if (count <= currentBuffer.Length)
            
                for (int i = 0; i < count; i++)
                
                    buffer[i] = this.currentBuffer[i];
                
                return count;
            
        
        return 0;
    

    public WaveFormat WaveFormat
    
        get  return this.waveFormat; 
    

我已经完成了这种(混乱)方式,因为否则我将不得不根据样本字节数等从 asio 缓冲区复制字节(查看 GetAsInterleavedSamples(...) 方法的源代码)。为了简单起见,我使用了 BufferedWaveProvider 来确保信号链的输出端有足够的(填充的)缓冲区,即使我并不真正需要它,但它是安全的。 在此提供者之后的几个处理块之后,链最终出现在最后一个提供者“输出”中。最后一个提供者被传递到

asioOut.InitRecordAndPlayback(output, this.InputChannels, this.SampleRate);

初始化整个对象时。 即使我在链中使用了许多处理块,我也没有可听到的下降或嗡嗡声,asio 缓冲区大小为 512 个样本。但我认为这真的取决于所使用的 Asio 硬件。 对我来说最重要的是确保输入和输出同步。

比较:如果我以相同的方式使用 WaveIn/WaveOutEvent,我可以达到几乎相同的延迟(在相同的廉价硬件上),但由于我的测试也是在两个单独的声音设备上进行的,所以输入缓冲区持续时间会在一段时间后增加由于一些下降或非同步音频时钟;)即使在 WPF 应用程序中使用时,为了达到非常低的延迟,我必须修补 WaveOutEvent 类以将播放线程的优先级提高到尽可能高,这有助于“对抗”大多数可能的 GC 中断.

目前看来,使用 Asio 接口我已经完全解决了这个 GC 问题。

希望这会有所帮助。

【讨论】:

以上是关于NAudio:使用 ASIO 录制音频并用吉他输出的主要内容,如果未能解决你的问题,请参考以下文章

Naudio Asio 在多个通道上同时录制和播放

NAudio Asio 录制播放和保存(仅限噪音)

NAudio Asio 同时录制和播放

在 NAudio 中列出 ASIO 音频输出设备

尝试在 ASIO 输出上使用 NAudio 播放音频时的空引用

如何使用 NAudio 实时计算 FFT(ASIO 输出)