如何用NAudio实现内存循环存储音频并写入文件?
解决NAudio缓存最近15秒音频并保存的问题
嘿,我来帮你搞定这个NAudio的音频缓存和保存问题!你的核心需求是在内存里只保留最近15秒的音频,点击按钮时把这段音频存成MP3文件,之前的代码主要有几个问题:没有做循环缓存裁剪、用错了文件写入器,还有线程安全的隐患。下面一步步给你解决:
核心思路
- 循环缓存控制:计算15秒音频对应的字节容量,每次新数据写入后,检查缓存总长度,超过就移除最早的部分,确保始终只保留最近15秒。
- 线程安全处理:音频捕获的
DataAvailable事件是在后台线程触发的,保存时必须加锁避免读写冲突。 - 正确保存MP3:NAudio的
WaveFileWriter只能写WAV格式,要存MP3得用LameMP3FileWriter(需要额外安装NuGet包)。
完整实现代码
首先,先声明必要的全局变量:
using NAudio.Wave; using NAudio.Lame; // 需要安装NAudio.Lame NuGet包 using System.IO; using System.Windows.Forms; // 如果是WinForms项目 private WasapiLoopbackCapture _voiceCapturer; private MemoryStream _circularAudioBuffer; private long _maxBufferSize; // 15秒音频对应的最大字节数 private readonly object _bufferLock = new object(); // 线程锁
1. 初始化音频捕获和缓存
在窗体加载或者初始化方法里执行:
private void Form_Load(object sender, EventArgs e) { // 获取默认输出设备(比如扬声器) var outputDevice = WasapiLoopbackCapture.GetDefaultLoopbackCaptureDevice(); _voiceCapturer = new WasapiLoopbackCapture(outputDevice); // 绑定数据接收事件 _voiceCapturer.DataAvailable += OnVoiceDataReceived; // 计算15秒音频的最大字节容量 _maxBufferSize = _voiceCapturer.WaveFormat.AverageBytesPerSecond * 15; _circularAudioBuffer = new MemoryStream((int)_maxBufferSize); // 预分配空间提升性能 // 开始捕获音频 _voiceCapturer.StartRecording(); }
2. 处理音频数据,维护循环缓存
这个方法会在后台线程触发,负责写入新数据并裁剪旧数据:
private void OnVoiceDataReceived(object sender, WaveInEventArgs e) { lock (_bufferLock) // 加锁避免和保存操作冲突 { // 写入新捕获的音频数据 _circularAudioBuffer.Write(e.Buffer, 0, e.BytesRecorded); // 如果缓存超过15秒的容量,移除最早的部分 while (_circularAudioBuffer.Length > _maxBufferSize) { var excessBytes = _circularAudioBuffer.Length - _maxBufferSize; var tempBuffer = new byte[_circularAudioBuffer.Length - excessBytes]; // 跳转到要保留的音频起始位置 _circularAudioBuffer.Position = excessBytes; // 读取保留的音频到临时缓冲区 _circularAudioBuffer.Read(tempBuffer, 0, tempBuffer.Length); // 重置缓存流,写入保留的音频 _circularAudioBuffer.SetLength(0); _circularAudioBuffer.Write(tempBuffer, 0, tempBuffer.Length); // 把流位置移到末尾,准备下次写入 _circularAudioBuffer.Position = _circularAudioBuffer.Length; } } }
3. 按钮点击保存音频
给按钮绑定点击事件,执行保存逻辑:
private void btnSaveAudio_Click(object sender, EventArgs e) { lock (_bufferLock) { // 先停止捕获,避免保存时继续写入数据 _voiceCapturer.StopRecording(); // 复制缓存内容到临时流,避免修改原缓存的位置 using var tempStream = new MemoryStream(); _circularAudioBuffer.Position = 0; _circularAudioBuffer.CopyTo(tempStream); tempStream.Position = 0; // 转换为可用的Wave流 using var rawStream = new RawSourceWaveStream(tempStream, _voiceCapturer.WaveFormat); using var sampleProvider = new WaveToSampleProvider(rawStream); // 用Lame编码器保存为MP3文件 using var mp3Writer = new LameMP3FileWriter( "output.mp3", _voiceCapturer.WaveFormat, LAMEPreset.VBR_90 // 可以根据需求调整音质预设 ); sampleProvider.WriteToWaveStream(mp3Writer); // 重新开始捕获,继续缓存后续音频 _voiceCapturer.StartRecording(); } MessageBox.Show("最近15秒音频已成功保存为MP3!"); }
你之前代码的问题分析
- 无循环缓存:原来的
MemoryStream会无限增长,没有裁剪旧数据,无法只保留最近15秒。 - 错误的文件写入器:用
WaveFileWriter写MP3是无效的,它只能生成WAV格式,MP3必须用LameMP3FileWriter。 - 流位置未处理:写入数据后
MemoryStream的Position在末尾,直接读取会获取不到内容。 - 线程不安全:后台线程写入缓存时,主线程保存可能导致数据混乱,必须加锁。
注意事项
- 记得安装NuGet包:
NAudio和NAudio.Lame。 - 如果是控制台项目,把WinForms相关代码替换成控制台的触发逻辑(比如用
Console.ReadLine()触发保存)。 - 可以根据需求调整MP3的音质预设(比如
LAMEPreset.CBR_128是固定比特率128kbps)。
内容的提问来源于stack exchange,提问作者botiapa
相关产品推荐
相关产品推荐

