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

NAudio C#:停止DirectSoundOut后录音意外终止的问题求助

解决停止监听不中断录音的问题

问题根源

你直接用WaveInProvider包装正在录制的WaveInEvent实现监听,这种方式会让播放组件(DirectSoundOut)和录制组件产生强绑定。当停止并释放DirectSoundOut时,会间接触发WaveInEvent的停止逻辑,导致录音中断。

解决方案

改用BufferedWaveProvider作为音频数据的中转层,让录制的音频同时写入文件和缓冲区,监听组件从缓冲区读取数据播放。这样录制和监听的数据流完全分离,停止监听不会影响录制进程。

修改后的代码

1. 新增全局缓冲区列表

private List<BufferedWaveProvider> _bufferedProviders = new List<BufferedWaveProvider>();
private DirectSoundOut _currentWaveOut = null; // 替换原waveOut变量,命名更清晰

2. 更新录制方法RecordMicNAudio

在数据可用事件中同时写入文件和缓冲区:

void RecordMicNAudio(int deviceNum, IProgress<int> progress, string StationName, WaveFileWriter wfWriter, WaveInEvent waveSource, Label lblCurrentStatus)
{
    try
    {
        SetControlText(lblCurrentStatus, "Recording...");

        waveSource.DeviceNumber = deviceNum;
        var waveFormat = new WaveFormat(sampleRate, bits: 16, WaveIn.GetCapabilities(deviceNum).Channels);
        waveSource.WaveFormat = waveFormat;

        // 初始化对应设备的缓冲提供者
        while (_bufferedProviders.Count <= deviceNum)
        {
            _bufferedProviders.Add(null);
        }
        var bufferedProvider = new BufferedWaveProvider(waveFormat)
        {
            BufferDuration = TimeSpan.FromSeconds(5), // 设置5秒缓冲区,避免溢出
            DiscardOnBufferOverflow = true // 缓冲区满时丢弃旧数据,不阻塞录制
        };
        _bufferedProviders[deviceNum] = bufferedProvider;

        waveSource.DataAvailable += (_, e) =>
        {
            if (wfWriter == null)
            {
                startTime = DateTime.Now;
                wfWriter = new WaveFileWriter(StationName + " " + startTime.ToString("yyyy-MM-dd_HH-mm-ss") + ".wav", waveSource.WaveFormat);
            }
            else
            {
                TimeSpan elapsed = DateTime.Now - startTime;
                if (elapsed.TotalMinutes >= 5)
                {
                    wfWriter.Dispose();
                    wfWriter = null;
                }
            }
            if (wfWriter != null)
            {
                wfWriter.Write(e.Buffer, 0, e.BytesRecorded);
                wfWriter.Flush();
            }

            // 将音频数据写入缓冲区,供监听使用
            bufferedProvider.AddSamples(e.Buffer, 0, e.BytesRecorded);

            // 音量计逻辑保持不变
            if (progress != null)
            {
                int bytesPerSample = waveSource.WaveFormat.BitsPerSample / 8;
                int newSampleCount = e.BytesRecorded / bytesPerSample;
                double[] buffer = new double[newSampleCount];

                double peak = 0;
                for (int i = 0; i < newSampleCount; i++)
                {
                    buffer[i] = BitConverter.ToInt16(e.Buffer, i * bytesPerSample);
                    peak = Math.Max(peak, buffer[i]);
                }

                var prp = peak / (1 << 15);
                var pVal = (int)(prp * 100);

                if (IsHandleCreated)
                {
                    if (InvokeRequired)
                    {
                        progress.Report(pVal);
                    }
                    else
                        progress.Report(pVal);
                }
            }
        };

        waveSource.RecordingStopped += (_, _) =>
        {
            waveSource?.Dispose();
            wfWriter?.Dispose();
            SetControlText(lblCurrentStatus, "Recording Stopped!!!");
            // 清空对应设备的缓冲区引用
            if (deviceNum < _bufferedProviders.Count)
            {
                _bufferedProviders[deviceNum] = null;
            }
        };

        waveSource.StartRecording();
    }
    catch (Exception ex)
    {
        SetControlText(lblCurrentStatus, "Error Occured!" + ex.Message);
    }
}

3. 更新监听开启方法btnSpeakerOn_Click

从缓冲区获取数据进行播放:

private void btnSpeakerOn_Click(object sender, EventArgs e)
{
    Button btn = sender as Button;
    var extractedString = Regex.Match(btn.Name, @"\d+").Value;
    var index = !string.IsNullOrEmpty(extractedString) ? Convert.ToInt32(extractedString) : 0;

    foreach (var item in btnSpeak)
    {
        if (item == btnSpeak[index])
        {
            item.Visible = false;
            btnMute[index].Visible = true;
        }
        else
            item.Enabled = false;
    }

    // 先停止之前的播放(如果存在)
    if (_currentWaveOut != null)
    {
        _currentWaveOut.Stop();
        _currentWaveOut.Dispose();
        _currentWaveOut = null;
    }

    // 获取对应设备的缓冲提供者
    var bufferedProvider = index < _bufferedProviders.Count ? _bufferedProviders[index] : null;
    if (bufferedProvider == null)
    {
        // 无对应录制设备,直接返回
        return;
    }

    _ = Task.Run(() =>
    {
        _currentWaveOut = new DirectSoundOut();
        _currentWaveOut.Init(bufferedProvider);
        _currentWaveOut.Play();
    });
}

4. 更新监听关闭方法btnSpeakerOff_Click

仅操作播放组件,不影响录制:

private void btnSpeakerOff_Click(object sender, EventArgs e)
{
    try
    {
        if (_currentWaveOut != null)
        {
            foreach (var item in btnSpeak)
            {
                item.Visible = true;
                item.Enabled = true;
            }

            _currentWaveOut.Stop();
            _currentWaveOut.Dispose();
            _currentWaveOut = null;
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

关键修改说明

  • 数据分离:BufferedWaveProvider作为中间层,录制时数据同时写入文件和缓冲区,监听从缓冲区读取,两者完全独立。
  • 缓冲区配置:设置DiscardOnBufferOverflow = true,避免监听停止后缓冲区溢出导致录制异常。
  • 状态管理:维护每个麦克风对应的缓冲区,切换监听设备时能正确获取数据;停止监听仅操作播放组件,不会触发录制停止逻辑。

内容的提问来源于stack exchange,提问作者Timothy Ayodele

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 05:27:01