NAudio 的 BufferedWaveProvider 在录制和混合音频时会变满
NAudio's BufferedWaveProvider gets full when recording and mixing an audio
我在使用 NAudio 库中的 BufferedWaveProvider 时遇到问题。我正在录制 2 个音频设备(一个麦克风和一个扬声器),将它们合并为一个流并将其发送到编码器(用于视频)。
为此,我执行以下操作:
- 创建一个线程,我将使用
WasapiCapture
录制麦克风。
- 创建一个线程,我将在其中使用
WasapiLookbackCapture
录制扬声器的音频。 (我也使用 SilenceProvider
所以我的记录没有间隙)。
- 我想混合这 2 个音频,所以我必须确保它们具有相同的格式,所以我会检测所有这些音频设备中最好的 WaveFormat。在我的场景中,它是扬声器。所以我决定麦克风音频将通过
MediaFoundationResampler
以调整其格式,使其与扬声器的音频相同。
- 来自 Wasapi(Lookback)Capture 的每个音频块都被发送到
BufferedWaveProvider
。
- 然后,我还制作了一个
MixingSampleProvider
,其中我从每个记录线程传递 ISampleProvider
。所以我为麦克风传递 MediaFoundationResampler
,为扬声器传递 BufferedWaveProvider
。
- 在第三个线程的循环中,我从
MixingSampleProvider
读取数据,它应该在填充时异步清空 BufferedWaveProvider
(s)。
- 因为每个缓冲区可能不会完全同时被填满,我正在查看这两个缓冲区之间的最小公共持续时间是多少,并且我正在从混合样本提供程序中读取这个数量。
- 然后我将读取的内容排入队列,以便我的编码器在第 4 个线程中也将并行处理它。
请看下面的流程图,它说明了我上面的描述。
我的问题如下:
- 在玩使用麦克风的视频游戏(在线多人游戏)时录制麦克风和扬声器超过 1 小时时效果很好。没有崩溃。缓冲区一直都很空。厉害了。
- 但出于某种原因,每次我尝试使用我的应用
during
进行 Discord、Skype 或 Teams 音频对话时,我都会立即(在 5 秒内)在 BufferedWaveProvider.AppSamples
上崩溃,因为缓冲区已满。
在调试模式下查看它,我可以看到:
- 演讲者对应的缓冲区几乎为空。平均最长 100 毫秒的音频。
- 麦克风(我重新采样的那个)对应的缓冲区已满(5秒)。
根据我在 NAudio 作者的博客、文档和 Whosebug 上阅读的内容,我认为我正在做最佳实践(但我可能是错的),即从线程写入缓冲区,然后读取它与另一个平行。当然存在它被填充的速度比我阅读它的速度更快的风险,这基本上就是现在正在发生的事情。但是我不明白为什么。
需要帮助
我需要一些帮助来了解我在这里缺少的东西。以下几点让我感到困惑:
为什么这个问题只发生在 Discord/Skype/Teams 会议上?我正在使用的视频游戏也使用麦克风,所以我无法想象它像 another app is preventing the microphone/speakers to works correctly
.
我同步两个录音机的启动。为此,我使用一个信号要求记录器启动,当它们都开始生成数据时(通过 DataAvailable
事件),我发送一个信号告诉它们用它们填充缓冲区会在下次活动中领取。它可能并不完美,因为两个音频设备在不同时间发送它们的 DataAvailable
,但我们谈论的是 60 毫秒的最大差异(在我的机器上),而不是 5 秒。所以我不明白为什么它会被填满。
为了理解我在 #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 :(
看来麦克风的缓冲区填充得更快了...但是为什么呢?可能是因为重采样器减慢了麦克风缓冲区的读取速度吗?如果是这样,它也应该减慢扬声器缓冲区的读取速度,因为我正在通过 MixingSampleProvider
读取它,不是吗?
如果有帮助的话,这里是我的代码的简化摘录:
/* THREAD #1 AND #2 */
_audioCapturer = new WasapiCapture(_device); // Or WasapiLookbackCapture + SilenceProvider playing
_audioCapturer.DataAvailable += AudioCapturer_DataAvailable;
// This buffer can host up to 5 second of audio, after that it crashed when calling AddSamples.
// So we should make sure we don't store more than this amount.
_waveBuffer = new BufferedWaveProvider(_audioCapturer.WaveFormat)
{
DiscardOnBufferOverflow = false,
ReadFully = false
};
if (DoINeedToResample)
{
// Create a resampler to adapt the audio to the desired wave format.
// In my scenario explained above, this happens for the Microphone.
_resampler = new MediaFoundationResampler(_waveBuffer, targettedWaveFormat);
}
else
{
// No conversion is required.
// In my scenario explained above, this happens for the Speakers.
_resampler = _waveBuffer;
}
private void AudioCapturer_DataAvailable(object? sender, WaveInEventArgs e)
{
NotifyRecorderIsReady();
if (!AllRecorderAreReady)
{
// Don't record the frame unless every other recorders have started to record too.
return;
}
// Add the captured sample to the wave buffer.
_waveBuffer.AddSamples(e.Buffer, 0, e.BytesRecorded);
// Notify the "mixer" that a chunk has been recorded.
}
/* The Mixer, in another class */
_waveProvider = new MixingSampleProvider(_allAudioRecoders.Select(r => r._resampler));
_allAudioRecoders.ForEach(r => r._audioCapturer.StartRecording());
Task _mixingTask = Task.CompletedTask;
private void OnChunkAddedToBufferedWaveProvider()
{
if (_mixingTask.IsCanceled
|| _mixingTask.IsCompleted
|| _mixingTask.IsFaulted
|| _mixingTask.IsCompletedSuccessfully)
{
// Treat the buffered audio in parallel.
_mixingTask = Task.Run(() =>
{
/* THREAD #3 */
lock (_lockObject)
{
TimeSpan minimalBufferedDuration;
do
{
// Gets the common duration of sound that all audio recorder captured.
minimalBufferedDuration = _allAudioRecoders.OrderBy(t => t._waveBuffer.Ticks).First().BufferedDuration;
if (minimalBufferedDuration.Ticks > 0)
{
// Read a sample from the mixer.
var bufferLength = minimalBufferedDuration.TotalSeconds * _waveProvider!.WaveFormat.AverageBytesPerSecond;
var data = new byte[(int)bufferLength];
var readData = _waveProvider.Read(data, 0, data.Length);
// Send the data to a queue that will be treated in parallel by the encoder.
}
} while (minimalBufferedDuration.Ticks > 0);
}
});
}
}
有没有人知道我做错了什么and/or为什么只有在 Discord/Skype/Teams 上通过语音聊天而不是在线多人游戏时才会重现?
提前致谢!
[更新] 2/9/2021
我可能已经发现了问题,但我不是 100% 确定如何处理它。
似乎我停止从麦克风接收数据,因此扬声器缓冲区已满。 (好像昨天,刚好相反)
[更新] 2/12/2021
这听起来像是出于某种原因,也许(我说也许是因为问题可能是其他原因)BufferedWaveProvider
在某些情况下阅读后不会自行清除。
让我想到的是:
- 在阅读
MixingSampleProvider
之前,我记录了多少缓冲持续时间我们有每个缓冲区。
- 我也是看完后记录下来的
- 大多数时候,这很好,我得到持续显示以下模式的持续数据,持续了十几分钟,甚至一个小时:
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? ...
- 然后突然间其中一个缓冲区将在 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 -->
我可以通过在缓冲区开始不再同步时清除缓冲区来进行脏修复,但我真的很想了解为什么会发生这种情况,以及是否有更好的解决方法。
谢谢
[更新]#2
好的,我想我已经解决了这个问题。这可能是 NAudio 库中的错误。这是我所做的:
- 照常播放我的程序。
- 当其中一个缓冲区达到 5 秒(又名变满)时,停止填充该特定缓冲区。
- 通过这样做,我最终会遇到这样一种情况,即 1 个设备的缓冲区已被填满,而另一个设备的缓冲区未被填满,但我会尽可能地继续读取这些缓冲区。
- 这是我发现的:似乎已满的缓冲区大小在读取后从未减少,这解释了为什么它突然变满。不幸的是,它不一致,无法解释原因。
在 GitHub 上进行更多调查和 post:https://github.com/naudio/NAudio/issues/742
我发现我应该监听 MixingSampleProvider.MixerInputEnded
事件并在它发生时将 SampleProvider 重新添加到 MixingSampleProvider。
发生这种情况的原因是我在捕获音频的同时处理音频,有时我处理它的速度可能比录制它的速度快,因此 MixingSampleProvider 认为它没有更多可读取的内容并停止.所以我应该告诉它不,这还没有结束,它应该期待更多。
我在使用 NAudio 库中的 BufferedWaveProvider 时遇到问题。我正在录制 2 个音频设备(一个麦克风和一个扬声器),将它们合并为一个流并将其发送到编码器(用于视频)。
为此,我执行以下操作:
- 创建一个线程,我将使用
WasapiCapture
录制麦克风。 - 创建一个线程,我将在其中使用
WasapiLookbackCapture
录制扬声器的音频。 (我也使用SilenceProvider
所以我的记录没有间隙)。 - 我想混合这 2 个音频,所以我必须确保它们具有相同的格式,所以我会检测所有这些音频设备中最好的 WaveFormat。在我的场景中,它是扬声器。所以我决定麦克风音频将通过
MediaFoundationResampler
以调整其格式,使其与扬声器的音频相同。 - 来自 Wasapi(Lookback)Capture 的每个音频块都被发送到
BufferedWaveProvider
。 - 然后,我还制作了一个
MixingSampleProvider
,其中我从每个记录线程传递ISampleProvider
。所以我为麦克风传递MediaFoundationResampler
,为扬声器传递BufferedWaveProvider
。 - 在第三个线程的循环中,我从
MixingSampleProvider
读取数据,它应该在填充时异步清空BufferedWaveProvider
(s)。 - 因为每个缓冲区可能不会完全同时被填满,我正在查看这两个缓冲区之间的最小公共持续时间是多少,并且我正在从混合样本提供程序中读取这个数量。
- 然后我将读取的内容排入队列,以便我的编码器在第 4 个线程中也将并行处理它。
请看下面的流程图,它说明了我上面的描述。
我的问题如下:
- 在玩使用麦克风的视频游戏(在线多人游戏)时录制麦克风和扬声器超过 1 小时时效果很好。没有崩溃。缓冲区一直都很空。厉害了。
- 但出于某种原因,每次我尝试使用我的应用
during
进行 Discord、Skype 或 Teams 音频对话时,我都会立即(在 5 秒内)在BufferedWaveProvider.AppSamples
上崩溃,因为缓冲区已满。
在调试模式下查看它,我可以看到:
- 演讲者对应的缓冲区几乎为空。平均最长 100 毫秒的音频。
- 麦克风(我重新采样的那个)对应的缓冲区已满(5秒)。
根据我在 NAudio 作者的博客、文档和 Whosebug 上阅读的内容,我认为我正在做最佳实践(但我可能是错的),即从线程写入缓冲区,然后读取它与另一个平行。当然存在它被填充的速度比我阅读它的速度更快的风险,这基本上就是现在正在发生的事情。但是我不明白为什么。
需要帮助
我需要一些帮助来了解我在这里缺少的东西。以下几点让我感到困惑:
为什么这个问题只发生在 Discord/Skype/Teams 会议上?我正在使用的视频游戏也使用麦克风,所以我无法想象它像
another app is preventing the microphone/speakers to works correctly
.我同步两个录音机的启动。为此,我使用一个信号要求记录器启动,当它们都开始生成数据时(通过
DataAvailable
事件),我发送一个信号告诉它们用它们填充缓冲区会在下次活动中领取。它可能并不完美,因为两个音频设备在不同时间发送它们的DataAvailable
,但我们谈论的是 60 毫秒的最大差异(在我的机器上),而不是 5 秒。所以我不明白为什么它会被填满。为了理解我在 #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 :(
看来麦克风的缓冲区填充得更快了...但是为什么呢?可能是因为重采样器减慢了麦克风缓冲区的读取速度吗?如果是这样,它也应该减慢扬声器缓冲区的读取速度,因为我正在通过 MixingSampleProvider
读取它,不是吗?
如果有帮助的话,这里是我的代码的简化摘录:
/* THREAD #1 AND #2 */
_audioCapturer = new WasapiCapture(_device); // Or WasapiLookbackCapture + SilenceProvider playing
_audioCapturer.DataAvailable += AudioCapturer_DataAvailable;
// This buffer can host up to 5 second of audio, after that it crashed when calling AddSamples.
// So we should make sure we don't store more than this amount.
_waveBuffer = new BufferedWaveProvider(_audioCapturer.WaveFormat)
{
DiscardOnBufferOverflow = false,
ReadFully = false
};
if (DoINeedToResample)
{
// Create a resampler to adapt the audio to the desired wave format.
// In my scenario explained above, this happens for the Microphone.
_resampler = new MediaFoundationResampler(_waveBuffer, targettedWaveFormat);
}
else
{
// No conversion is required.
// In my scenario explained above, this happens for the Speakers.
_resampler = _waveBuffer;
}
private void AudioCapturer_DataAvailable(object? sender, WaveInEventArgs e)
{
NotifyRecorderIsReady();
if (!AllRecorderAreReady)
{
// Don't record the frame unless every other recorders have started to record too.
return;
}
// Add the captured sample to the wave buffer.
_waveBuffer.AddSamples(e.Buffer, 0, e.BytesRecorded);
// Notify the "mixer" that a chunk has been recorded.
}
/* The Mixer, in another class */
_waveProvider = new MixingSampleProvider(_allAudioRecoders.Select(r => r._resampler));
_allAudioRecoders.ForEach(r => r._audioCapturer.StartRecording());
Task _mixingTask = Task.CompletedTask;
private void OnChunkAddedToBufferedWaveProvider()
{
if (_mixingTask.IsCanceled
|| _mixingTask.IsCompleted
|| _mixingTask.IsFaulted
|| _mixingTask.IsCompletedSuccessfully)
{
// Treat the buffered audio in parallel.
_mixingTask = Task.Run(() =>
{
/* THREAD #3 */
lock (_lockObject)
{
TimeSpan minimalBufferedDuration;
do
{
// Gets the common duration of sound that all audio recorder captured.
minimalBufferedDuration = _allAudioRecoders.OrderBy(t => t._waveBuffer.Ticks).First().BufferedDuration;
if (minimalBufferedDuration.Ticks > 0)
{
// Read a sample from the mixer.
var bufferLength = minimalBufferedDuration.TotalSeconds * _waveProvider!.WaveFormat.AverageBytesPerSecond;
var data = new byte[(int)bufferLength];
var readData = _waveProvider.Read(data, 0, data.Length);
// Send the data to a queue that will be treated in parallel by the encoder.
}
} while (minimalBufferedDuration.Ticks > 0);
}
});
}
}
有没有人知道我做错了什么and/or为什么只有在 Discord/Skype/Teams 上通过语音聊天而不是在线多人游戏时才会重现?
提前致谢!
[更新] 2/9/2021
我可能已经发现了问题,但我不是 100% 确定如何处理它。 似乎我停止从麦克风接收数据,因此扬声器缓冲区已满。 (好像昨天,刚好相反)
[更新] 2/12/2021
这听起来像是出于某种原因,也许(我说也许是因为问题可能是其他原因)BufferedWaveProvider
在某些情况下阅读后不会自行清除。
让我想到的是:
- 在阅读
MixingSampleProvider
之前,我记录了多少缓冲持续时间我们有每个缓冲区。 - 我也是看完后记录下来的
- 大多数时候,这很好,我得到持续显示以下模式的持续数据,持续了十几分钟,甚至一个小时:
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? ...
- 然后突然间其中一个缓冲区将在 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 -->
我可以通过在缓冲区开始不再同步时清除缓冲区来进行脏修复,但我真的很想了解为什么会发生这种情况,以及是否有更好的解决方法。
谢谢
[更新]#2
好的,我想我已经解决了这个问题。这可能是 NAudio 库中的错误。这是我所做的:
- 照常播放我的程序。
- 当其中一个缓冲区达到 5 秒(又名变满)时,停止填充该特定缓冲区。
- 通过这样做,我最终会遇到这样一种情况,即 1 个设备的缓冲区已被填满,而另一个设备的缓冲区未被填满,但我会尽可能地继续读取这些缓冲区。
- 这是我发现的:似乎已满的缓冲区大小在读取后从未减少,这解释了为什么它突然变满。不幸的是,它不一致,无法解释原因。
在 GitHub 上进行更多调查和 post:https://github.com/naudio/NAudio/issues/742
我发现我应该监听 MixingSampleProvider.MixerInputEnded
事件并在它发生时将 SampleProvider 重新添加到 MixingSampleProvider。
发生这种情况的原因是我在捕获音频的同时处理音频,有时我处理它的速度可能比录制它的速度快,因此 MixingSampleProvider 认为它没有更多可读取的内容并停止.所以我应该告诉它不,这还没有结束,它应该期待更多。