tl;dr 我想将
asio_DataAvailable(object sender, AsioAudioAvailableEventArgs e)
事件中的数据放入 BufferedWaveProvider
中并回放它们。
你好,
我正在尝试制作一个应用程序,它将音频录制到文件中,但也会在录制时将其播放给用户。我设法使用 WaveInEvent 和 WaveOutEvent 以及 BufferedWaveProvider 来做到这一点。但我认为 ASIO 会有更好的延迟,所以我想用它做同样的事情。
我在这里找到了关于这个主题的问题,但没有一个包含完整的答案,我无法根据它们解决问题。您能否发布完整的代码工作示例?
我尝试了 Marshal.Copy 的版本,但得到了 IndexOutOfRangeException。缓冲区(
e.InputBuffers[i]
)似乎没有足够的样本。我还想进一步处理音频(将其保存到文件中),同时播放另一个音频(使用 MixingSampleProvider),因此仅简单复制缓冲区可能对我不起作用。
ASIO 驱动程序模型基于单个回调,您可以在其中访问记录的缓冲区并写入播放缓冲区。这对于超低延迟播放非常有用,但是
InputBuffers
只是每个通道的块非托管内存的 IntPtr。因此该数组的大小是输入通道的数量,而不是样本的数量。您需要了解一些有关指针解引用和位操作的知识才能成功使用它们,因为 ASIO 支持多种不同的位深度和样本格式,如果您想访问这样的低级音频,则必须使用正确的位深度和样本格式。
我使用 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 源代码中获取的部分可能已经优化了。