NAudio 的 BufferedWaveProvider 在录制和混合音频时变满

Eti*_*oux 6 c# audio audio-streaming naudio wasapi

我遇到了来自 NAudio 库的 BufferedWaveProvider 的问题。我正在录制 2 个音频设备(一个麦克风和一个扬声器),将它们合并为 1 个流并将其发送到编码器(用于视频)。

为此,我执行以下操作:

  1. 创建一个线程,我将使用WasapiCapture.
  2. 创建一个线程,我将在其中使用WasapiLookbackCapture. (我也使用 aSilenceProvider所以我记录的内容没有空白)。
  3. 我想混合这两个音频,所以我必须确保它们具有相同的格式,所以我检测所有这些音频设备中最好的 WaveFormat 是什么。在我的场景中,它是扬声器。所以我决定麦克风音频将通过 aMediaFoundationResampler来调整其格式,使其与扬声器中的音频相同。
  4. Wasapi(Lookback)Capture 中的每个音频块都发送到BufferedWaveProvider.
  5. 然后,我还制作了一个MixingSampleProviderISampleProvider从每个录制线程传递的地方。所以我通过MediaFoundationResampler麦克风和BufferedWaveProvider扬声器。
  6. 在第三个线程的循环中,我从 读取数据MixingSampleProvider,它应该BufferedWaveProvider在填充时异步清空(s)。
  7. 因为每个缓冲区可能不会完全同时填充,所以我正在查看这两个缓冲区之间的最小共同持续时间是多少,并且我正在从混合样本提供程序中读取这个数量。
  8. 然后我将阅读的内容排入队列,以便我的编码器在第 4 个线程中也将并行处理它。

请参阅下面的流程图,它说明了我上面的描述。

在此处输入图片说明

我的问题如下:

  • 在玩也使用麦克风的视频游戏(用于在线多人游戏)时,录制麦克风和扬声器超过 1 小时时,它的效果非常好。没有崩溃。缓冲区一直很空。这很棒。
  • 但出于某种原因,每次我尝试使用我的应用during进行 Discord、Skype 或 Teams 音频对话时,我都会立即(在 5 秒内)崩溃,BufferedWaveProvider.AppSamples因为缓冲区已满。

在调试模式下查看它,我可以看到:

  • 扬声器对应的缓冲区几乎是空的。它的平均音频最长为 100 毫秒。
  • 与麦克风(我重新采样的那个)对应的缓冲区已满(5 秒)。

从我在 NAudio 的作者博客、文档和 StackOverflow 上阅读的内容来看,我认为我正在做最佳实践(但我可能是错的),即从一个线程写入缓冲区,并从另一个线程并行读取它. 当然有一种风险,它比我阅读它的速度更快,这基本上就是现在正在发生的事情。但我不明白为什么。

需要帮助

我需要一些帮助来理解我在这里遗漏了什么,请。以下几点让我感到困惑:

  1. 为什么这个问题只发生在 Discord/Skype/Teams 会议上?我正在使用的视频游戏也在使用麦克风,所以我无法想象它像another app is preventing the microphone/speakers to works correctly.

  2. 我同步两个录音机的启动。这样做,我使用一个信号来要求记录器启动,当它们都开始生成数据(通过DataAvailable事件)时,我发送一个信号告诉他们用他们将在下一个接收到的内容填充缓冲区事件。这可能并不完美,因为两个音频设备DataAvailable在不同的时间发送它们,但我们谈论的最大差异为 60 毫秒(在我的机器上),而不是 5 秒。所以我不明白为什么它会被填满。

  3. 为了回顾我在 #2 中所说的内容,我的遥测显示缓冲区正在以这种方式填充(值是虚拟的):

Microphone buffered duration: 0ms | Speakers: 0ms
Microphone buffered duration: 60ms | Speakers: 60ms
Microphone buffered duration: 0ms | Speakers: 0ms <= That's because I read the data from the mixing sample provider
Microphone buffered duration: 60ms | Speakers: 0ms <= Events may not be in sync, that's ok.
Microphone buffered duration: 120ms | Speakers: 60ms <= Alright, next loop, I'll extract 60ms on each buffer.
Microphone buffered duration: 390ms | Speakers: 0ms <= Wait, how?
Microphone buffered duration: 390ms | Speakers: 60ms
[...]
Microphone buffered duration: 5000ms | Speakers: 0ms <= Oh no :(
Run Code Online (Sandbox Code Playgroud)

所以看起来麦克风的缓冲区越来越快......但为什么呢?可能是因为重采样器减慢了麦克风缓冲区的读取速度?如果是这样,它也应该减慢扬声器缓冲区的读取速度,因为我正在通过 a 读取它MixingSampleProvider,不是吗?

如果可以的话,这是我的代码的简化摘录:

Microphone buffered duration: 0ms | Speakers: 0ms
Microphone buffered duration: 60ms | Speakers: 60ms
Microphone buffered duration: 0ms | Speakers: 0ms <= That's because I read the data from the mixing sample provider
Microphone buffered duration: 60ms | Speakers: 0ms <= Events may not be in sync, that's ok.
Microphone buffered duration: 120ms | Speakers: 60ms <= Alright, next loop, I'll extract 60ms on each buffer.
Microphone buffered duration: 390ms | Speakers: 0ms <= Wait, how?
Microphone buffered duration: 390ms | Speakers: 60ms
[...]
Microphone buffered duration: 5000ms | Speakers: 0ms <= Oh no :(
Run Code Online (Sandbox Code Playgroud)

有没有人知道我做错了什么和/或为什么只有在 Discord/Skype/Teams 上通过语音聊天而不是通过在线多人游戏时才会出现这种情况?

提前致谢!

[更新] 2/9/2021

我可能已经发现了这个问题,但我不是 100% 确定如何处理它。似乎我停止从麦克风接收数据,因此扬声器缓冲区已满。(好像昨天,正好相反)。

[更新] 2/12/2021

这听起来出于某种原因,也许(我说也许是因为问题可能是其他问题)BufferedWaveProvider在某些情况下阅读后并没有清除。

让我想到的是以下几点:

  1. 在阅读之前,MixingSampleProvider我记录了我们在每个缓冲区中的缓冲持续时间。
  2. 我也在阅读后记录下来。
  3. 大多数情况下,这很好,我得到的持续数据显示以下模式持续数十分钟,甚至一个小时:
BEFORE READING MICROPHONE: 20ms
BEFORE READING SPEAKER: 10ms
AFTER READING MICROPHONE: 0ms
AFTER READING SPEAKER: 0ms

// I don't explain why both buffer are empty considering my algorithm was supposed to read only 10ms, but the output MP4 seems fine and in sync, so it's fine? ...
Run Code Online (Sandbox Code Playgroud)
  1. 然后突然其中一个缓冲区将在 5 秒内被填满。
BEFORE READING MICROPHONE: 20ms
BEFORE READING SPEAKER: 10ms
AFTER READING MICROPHONE: 0ms
AFTER READING SPEAKER: 0ms
BEFORE READING MICROPHONE: 10ms
BEFORE READING SPEAKER: 20ms
AFTER READING MICROPHONE: 0ms
AFTER READING SPEAKER: 20ms
BEFORE READING MICROPHONE: 20ms
BEFORE READING SPEAKER: 30ms
AFTER READING MICROPHONE: 0ms
AFTER READING SPEAKER: 30ms
BEFORE READING MICROPHONE: 10ms
BEFORE READING SPEAKER: 50ms
AFTER READING MICROPHONE: 0ms
AFTER READING SPEAKER: 50ms
BEFORE READING MICROPHONE: 20ms
BEFORE READING SPEAKER: 70ms
AFTER READING MICROPHONE: 0ms
AFTER READING SPEAKER: 70ms
BEFORE READING MICROPHONE: 20ms
BEFORE READING SPEAKER: 80ms
AFTER READING MICROPHONE: 0ms
AFTER READING SPEAKER: 80ms
BEFORE READING MICROPHONE: 10ms
BEFORE READING SPEAKER: 100ms
AFTER READING MICROPHONE: 0ms
AFTER READING SPEAKER: 100ms
BEFORE READING MICROPHONE: 20ms
BEFORE READING SPEAKER: 110ms
AFTER READING MICROPHONE: 0ms
AFTER READING SPEAKER: 110ms
BEFORE READING MICROPHONE: 10ms
BEFORE READING SPEAKER: 130ms
AFTER READING MICROPHONE: 0ms
AFTER READING SPEAKER: 130ms
[...]
BEFORE READING MICROPHONE: 20ms
BEFORE READING SPEAKER: 4970ms
AFTER READING MICROPHONE: 0ms
AFTER READING SPEAKER: 4970ms
BEFORE READING MICROPHONE: 10ms
BEFORE READING SPEAKER: 4980ms
AFTER READING MICROPHONE: 0ms
AFTER READING SPEAKER: 4980ms
BEFORE READING MICROPHONE: 20ms
BEFORE READING SPEAKER: 5000ms
AFTER READING MICROPHONE: 0ms
AFTER READING SPEAKER: 5000ms
<!-- Crash -->
Run Code Online (Sandbox Code Playgroud)

当缓冲区开始不再同步时,我可以通过清除缓冲区来做一个肮脏的修复,但我真的很想了解为什么会发生这种情况,以及是否有更好的方法来解决它。

谢谢

[更新] #2

好的,我想我把问题隔离了。这可能是 NAudio 库中的一个错误。这是我所做的:

  1. 像往常一样播放我的节目。
  2. 当其中一个缓冲区达到 5 秒(即变满)时,停止填充该特定缓冲区。
  3. 通过这样做,我最终会遇到一种情况,即 1 个设备的缓冲区被填满,而另一个设备的缓冲区没有填满,但我会尽可能地继续读取这些缓冲区。
  4. 这是我发现的:看起来已满的缓冲区的大小在读取后从未减少,这解释了为什么突然变满。不幸的是,这是不一致的,无法解释原因。

Eti*_*oux 1

经过更多调查和 GitHub 上的帖子:https ://github.com/naudio/NAudio/issues/742

我发现我应该监听该MixingSampleProvider.MixerInputEnded事件,并在事件发生时将 SampleProvider 重新添加到 MixingSampleProvider 中。

发生这种情况的原因是我在捕获音频时处理音频,并且有时我处理它的速度可能比录制它的速度快,因此 MixingSampleProvider 认为它没有更多内容可读取并停止。所以我应该告诉它,不,一切还没有结束,它应该期待更多。