NAudio Asio 同时录音和播放

问题描述 投票:0回答:2

tl;dr 我想将

asio_DataAvailable(object sender, AsioAudioAvailableEventArgs e)
事件中的数据放入
BufferedWaveProvider
中并回放它们。

你好,

我正在尝试制作一个应用程序,它将音频录制到文件中,但也会在录制时将其播放给用户。我设法使用 WaveInEvent 和 WaveOutEvent 以及 BufferedWaveProvider 来做到这一点。但我认为 ASIO 会有更好的延迟,所以我想用它做同样的事情。

我在这里找到了关于这个主题的问题,但没有一个包含完整的答案,我无法根据它们解决问题。您能否发布完整的代码工作示例?

NAudio Asio 录音和播放

如何使用 AsioOut 进行 NAudio 录音和播放

我尝试了 Marshal.Copy 的版本,但得到了 IndexOutOfRangeException。缓冲区(

e.InputBuffers[i]
)似乎没有足够的样本。我还想进一步处理音频(将其保存到文件中),同时播放另一个音频(使用 MixingSampleProvider),因此仅简单复制缓冲区可能对我不起作用。

c# audio naudio asio
2个回答
0
投票

ASIO 驱动程序模型基于单个回调,您可以在其中访问记录的缓冲区并写入播放缓冲区。这对于超低延迟播放非常有用,但是

InputBuffers
只是每个通道的块非托管内存的 IntPtr。因此该数组的大小是输入通道的数量,而不是样本的数量。您需要了解一些有关指针解引用和位操作的知识才能成功使用它们,因为 ASIO 支持多种不同的位深度和样本格式,如果您想访问这样的低级音频,则必须使用正确的位深度和样本格式。


0
投票

我使用 MixingSampleProvider、自定义正弦波发生器和麦克风(通过 BufferedWaveProvider)制作了一个小测试应用程序。正弦波和麦克风都可以使用 AsioOut 通过扬声器听到。

初始化看起来像这样(我在这里简化了演示代码,删除了所有错误检查):

private ISampleProvider sineProvider;
private AsioOut asioOut;
private MixingSampleProvider mixer;
private BufferedWaveProvider micProvider;

private void initAsio()
{
  mixer = new MixingSampleProvider(WaveFormat.CreateIeeeFloatWaveFormat(48000,2));
  asioOut = new AsioOut("Realtek ASIO");
  sineProvider = new SineGenerator(500); // 500 Hz sinewave
  mixer.AddMixerInput(sineProvider);
  micProvider = new BufferedWaveProvider(WaveFormat.CreateIeeeFloatWaveFormat(48000,2));
  mixer.AddMixerInput(micProvider);
  asioOut.InputChannelOffset = 0;
  asioOut.InitRecordAndPlayback(mixer.ToWaveProvider(), 2, 48000);
  asioOut.AudioAvailable += OnAsioOutAudioAvailable;
  asioOut.Play();
}

困难的部分 - 将麦克风数据发送到缓冲波形提供程序 - 简单地改编自 NAudio 源代码中的“AsioAudioAvailableEventArgs.GetAsInterleavedSamples(float[]样本)”函数。原始函数给出了一个交错浮点数组(例如交错的左和右通道),我的修改包括以“字节”形式分解这些浮点,因此可以使用“AddSamples”方法将字节数组发送到 BufferedWaveProvider。

unsafe void OnAsioOutAudioAvailable(object sender, AsioAudioAvailableEventArgs audioInData)
{
  int numChannels = audioInData.InputBuffers.Length;
  byte[] BytesToSendOut = new byte[audioInData.SamplesPerBuffer * numChannels * 4];
  byte[] tempBytes;

  int num2 = 0;
  
  if (audioInData.AsioSampleType == AsioSampleType.Int32LSB)
  {
    for (int i = 0; i < audioInData.SamplesPerBuffer; i++)
    {
      for (int j = 0; j < numChannels; j++)
      {
        tempBytes = BitConverter.GetBytes((float)(*(int*)((byte*)(void*)audioInData.InputBuffers[j] + (nint)i * (nint)4)) / 2.14748365E+09f);
        BytesToSendOut[num2++] = tempBytes[0];
        BytesToSendOut[num2++] = tempBytes[1];
        BytesToSendOut[num2++] = tempBytes[2];
        BytesToSendOut[num2++] = tempBytes[3];
      }
    }
  }
  else if (audioInData.AsioSampleType == AsioSampleType.Int16LSB)
  {
    for (int k = 0; k < audioInData.SamplesPerBuffer; k++)
    {
      for (int l = 0; l < numChannels; l++)
      {
        tempBytes = BitConverter.GetBytes((float)(*(short*)((byte*)(void*)audioInData.InputBuffers[l] + (nint)k * (nint)2)) / 32767f);
        BytesToSendOut[num2++] = tempBytes[0];
        BytesToSendOut[num2++] = tempBytes[1];
        BytesToSendOut[num2++] = tempBytes[2];
        BytesToSendOut[num2++] = tempBytes[3];
      }
    }
  }
  else if (audioInData.AsioSampleType == AsioSampleType.Int24LSB)
  {
    for (int m = 0; m < audioInData.SamplesPerBuffer; m++)
    {
      for (int n = 0; n < numChannels; n++)
      {
        byte* ptr = (byte*)(void*)audioInData.InputBuffers[n] + m * 3;
        int num3 = *ptr | (ptr[1] << 8) | ((sbyte)ptr[2] << 16);
        tempBytes = BitConverter.GetBytes((float)num3 / 8388608f);
        BytesToSendOut[num2++] = tempBytes[0];
        BytesToSendOut[num2++] = tempBytes[1];
        BytesToSendOut[num2++] = tempBytes[2];
        BytesToSendOut[num2++] = tempBytes[3];
      }
    }
  }
  else if (audioInData.AsioSampleType == AsioSampleType.Float32LSB)
  { 
    for (int num4 = 0; num4 < audioInData.SamplesPerBuffer; num4++)
    {
      for (int num5 = 0; num5 < numChannels; num5++)
      {
        tempBytes = BitConverter.GetBytes(*(float*)((byte*)(void*)audioInData.InputBuffers[num5] + (nint)num4 * (nint)4));
        BytesToSendOut[num2++] = tempBytes[0];
        BytesToSendOut[num2++] = tempBytes[1];
        BytesToSendOut[num2++] = tempBytes[2];
        BytesToSendOut[num2++] = tempBytes[3];
      }
    }
  }
  else
  {
    // Unknown WaveFormat: output will be silent for the microphone track
  }
  micProvider.AddSamples(BytesToSendOut, 0, BytesToSendOut.Length);
  audioInData.WrittenToOutputBuffers = true;
}

“SineGenerator”类是我创建的自定义类,它可以替换为任何其他 SampleProvider 类型的类。

我没有在此演示中包含“关闭/处置”代码,您应该将其放置在代码中的适当位置。

也许我的代码可以优化,但从 NAudio 源代码中获取的部分可能已经优化了。

© www.soinside.com 2019 - 2024. All rights reserved.