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

如何用NAudio实现内存循环存储音频并写入文件?

解决NAudio缓存最近15秒音频并保存的问题

嘿,我来帮你搞定这个NAudio的音频缓存和保存问题!你的核心需求是在内存里只保留最近15秒的音频,点击按钮时把这段音频存成MP3文件,之前的代码主要有几个问题:没有做循环缓存裁剪、用错了文件写入器,还有线程安全的隐患。下面一步步给你解决:

核心思路

  1. 循环缓存控制:计算15秒音频对应的字节容量,每次新数据写入后,检查缓存总长度,超过就移除最早的部分,确保始终只保留最近15秒。
  2. 线程安全处理:音频捕获的DataAvailable事件是在后台线程触发的,保存时必须加锁避免读写冲突。
  3. 正确保存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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:04:26