使用NAudio录制麦克风与系统音频时出现爆音问题求助
NAudio同时录制麦克风+桌面音频出现爆音的问题分析与解决
问题描述
我希望将默认录制设备(当前麦克风)和默认播放设备(桌面音频)的声音录制到同一个文件中,但录制结果只有刺耳的爆音。设备本身工作正常,我有声卡,怀疑是NAudio的使用方式有误,请问问题出在哪里?
原始代码
static void Main(string[] args) { try { AudioRecorder recorder = new AudioRecorder(); while (true) { recorder.StartRecording(); Thread.Sleep(2000); } } catch (Exception e) { Console.WriteLine(e); Console.ReadLine(); } } public WaveInEvent microphoneInput; public WasapiLoopbackCapture systemSoundInput; public WaveFileWriter writer; public string outputPath = Directory.GetCurrentDirectory(); public int fileIndex = 0; public AudioRecorder() { // microphone input microphoneInput = new WaveInEvent(); // system sound input systemSoundInput = new WasapiLoopbackCapture(); // event handlers for data available microphoneInput.DataAvailable += OnDataAvailable; systemSoundInput.DataAvailable += OnDataAvailable; } public void OnDataAvailable(object sender, WaveInEventArgs e) { writer?.Write(e.Buffer, 0, e.BytesRecorded); } public void StartRecording() { string fileName = $"recording_{fileIndex++}.wav"; writer = new WaveFileWriter(outputPath + fileName, microphoneInput.WaveFormat); microphoneInput.StartRecording(); systemSoundInput.StartRecording(); // Record for 5 seconds Thread.Sleep(TimeSpan.FromSeconds(5)); StopRecording(); } public void StopRecording() { microphoneInput.StopRecording(); systemSoundInput.StopRecording(); writer?.Dispose(); writer = null; }
问题根源
- 音频格式不匹配:WaveInEvent(麦克风)和WasapiLoopbackCapture(桌面音频)的采样率、位深度、声道数几乎不可能完全一致,直接把两种格式的数据写入同一个WAV文件,会导致音频帧错位,产生爆音。
- 异步写入冲突:两个设备的
DataAvailable事件是异步触发的,交替写入WaveFileWriter会破坏WAV文件的连续帧结构,音频数据混乱就会出现爆音。 - 录制流程逻辑错误:Main方法里的while循环每隔2秒就调用
StartRecording,但StartRecording内部已经会Sleep5秒,导致前一次录制还没停止就启动新的录制,资源冲突进一步加剧问题。
解决方法
1. 统一音频格式
把两个输入设备的音频转换成相同的格式(比如44.1kHz、16位、立体声),用NAudio的格式转换流处理。
2. 正确混合音频数据
不要分别写入两个设备的数据,而是将它们的音频样本混合(相加后归一化避免过载),再写入文件,推荐用MixingWaveProvider32自动处理混合。
3. 修复录制流程
确保上一次录制完全结束后再启动下一次,用同步事件控制循环逻辑。
修正后的代码
using NAudio.Wave; using System; using System.IO; using System.Threading; using System.Diagnostics; class Program { static void Main(string[] args) { try { using (var recorder = new AudioRecorder()) { while (true) { recorder.StartRecording(); // 等待当前录制完成再启动下一次 recorder.RecordingCompleted.WaitOne(); // 可选:两次录制间隔2秒 Thread.Sleep(2000); } } } catch (Exception e) { Console.WriteLine(e); Console.ReadLine(); } } } public class AudioRecorder : IDisposable { private WaveInEvent _microphoneInput; private WasapiLoopbackCapture _systemSoundInput; private WaveFileWriter _writer; private readonly string _outputPath; private int _fileIndex; private readonly AutoResetEvent _recordingCompleted = new AutoResetEvent(false); private MixingWaveProvider32 _mixingProvider; public AutoResetEvent RecordingCompleted => _recordingCompleted; public AudioRecorder() { _outputPath = Directory.GetCurrentDirectory(); // 初始化麦克风和桌面音频输入 _microphoneInput = new WaveInEvent(); _systemSoundInput = new WasapiLoopbackCapture(); // 统一目标格式:44.1kHz、16位、立体声 var targetFormat = new WaveFormat(44100, 16, 2); // 将两个输入转换为目标格式 var micConverted = new WaveFormatConversionStream(targetFormat, new WaveInProvider(_microphoneInput)); var systemConverted = new WaveFormatConversionStream(targetFormat, new WaveInProvider(_systemSoundInput)); // 转换为32位浮点格式用于混合(避免混合时失真) var micFloat = new Wave16ToFloatProvider(micConverted); var systemFloat = new Wave16ToFloatProvider(systemConverted); // 创建混合提供者,自动处理音频流混合 _mixingProvider = new MixingWaveProvider32(); _mixingProvider.AddInputStream(micFloat); _mixingProvider.AddInputStream(systemFloat); } public void StartRecording() { string fileName = $"recording_{_fileIndex++}.wav"; var fullPath = Path.Combine(_outputPath, fileName); // 使用混合后的格式创建文件写入器 _writer = new WaveFileWriter(fullPath, _mixingProvider.WaveFormat); // 启动两个设备的录制 _microphoneInput.StartRecording(); _systemSoundInput.StartRecording(); // 在后台线程读取混合后的音频数据并写入文件 ThreadPool.QueueUserWorkItem(_ => { var buffer = new byte[_mixingProvider.WaveFormat.AverageBytesPerSecond / 2]; var stopwatch = Stopwatch.StartNew(); while (stopwatch.Elapsed < TimeSpan.FromSeconds(5)) { int bytesRead = _mixingProvider.Read(buffer, 0, buffer.Length); if (bytesRead > 0) { _writer.Write(buffer, 0, bytesRead); } Thread.Sleep(10); // 降低CPU占用 } StopRecording(); _recordingCompleted.Set(); // 通知录制完成 }); } public void StopRecording() { _microphoneInput.StopRecording(); _systemSoundInput.StopRecording(); _writer?.Flush(); _writer?.Dispose(); _writer = null; } public void Dispose() { _microphoneInput?.Dispose(); _systemSoundInput?.Dispose(); _writer?.Dispose(); _recordingCompleted.Dispose(); } }
关键修正点说明
- 用
MixingWaveProvider32处理两个音频流的混合,自动解决格式统一和样本叠加的问题,避免爆音。 - 用
AutoResetEvent同步录制完成事件,确保循环逻辑正确,不会出现多次录制重叠的情况。 - 在后台线程读取混合后的音频数据,避免阻塞主线程,同时保证数据连续写入文件,维护WAV文件的结构完整性。
内容的提问来源于stack exchange,提问作者boulemoo4
相关产品推荐
相关产品推荐

