You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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;
}

问题根源

  1. 音频格式不匹配:WaveInEvent(麦克风)和WasapiLoopbackCapture(桌面音频)的采样率、位深度、声道数几乎不可能完全一致,直接把两种格式的数据写入同一个WAV文件,会导致音频帧错位,产生爆音。
  2. 异步写入冲突:两个设备的DataAvailable事件是异步触发的,交替写入WaveFileWriter会破坏WAV文件的连续帧结构,音频数据混乱就会出现爆音。
  3. 录制流程逻辑错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 10:17:32