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
相关产品推荐
相关产品推荐

