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

NAudio每10秒重置WaveFileWriter的性能优化方案咨询

问题

我用C#结合NAudio编写代码,没有把音频写入本地WAVE文件,而是写入MemoryStream后发送给API,这部分运行正常。

现在需要每10秒向API发送一段音频数据块,我通过把MemoryStream加入ConcurrentQueue,每10秒重新创建MemoryStream和写入器的方式实现了需求,但性能不够理想。

我的OnDataAvailable()方法代码如下:

void OnDataAvailable(object sender, WaveInEventArgs e)
{
    if (InvokeRequired)
    {
        //Debug.WriteLine("Data Available");
        BeginInvoke(new EventHandler<WaveInEventArgs>(OnDataAvailable), sender, e);
    }
    else
    {
        //Debug.WriteLine("Flushing Data Available");
        writerMem.Write(e.Buffer, 0, e.BytesRecorded);
        //writer.Write(e.Buffer, 0, e.BytesRecorded);

        int secondsRecorded = (int)(writerMem.Length / writerMem.WaveFormat.AverageBytesPerSecond);

        if (secondsRecorded >= 10)
        {
            memQ.Enqueue(memStream);

            memStream = new MemoryStream();
            writerMem = new WaveFileWriter(memStream, new WaveFormat(44100, 16, 1));
        }
        else
        {
            progressBar1.Value = secondsRecorded;
        }
    }
}

请问有没有更优的方法重置写入器和对应的MemoryStream?


优化方案

1. 复用MemoryStream和WaveFileWriter,避免频繁创建销毁

频繁实例化MemoryStream和WaveFileWriter是核心性能瓶颈之一,可通过重置流位置并清空内容实现复用:

// 初始化时仅创建一次
private MemoryStream memStream = new MemoryStream();
private WaveFileWriter writerMem;

// 构造函数或初始化方法中创建写入器
public YourClass()
{
    // 用包装类避免WaveFileWriter销毁底层MemoryStream
    writerMem = new WaveFileWriter(new IgnoreDisposeStream(memStream), new WaveFormat(44100, 16, 1));
}

void OnDataAvailable(object sender, WaveInEventArgs e)
{
    if (InvokeRequired)
    {
        BeginInvoke(new EventHandler<WaveInEventArgs>(OnDataAvailable), sender, e);
        return;
    }

    writerMem.Write(e.Buffer, 0, e.BytesRecorded);

    int secondsRecorded = (int)(writerMem.Length / writerMem.WaveFormat.AverageBytesPerSecond);

    if (secondsRecorded >= 10)
    {
        // 复制当前流内容到新MemoryStream入队,防止后续写入覆盖
        var streamToEnqueue = new MemoryStream(memStream.ToArray());
        memQ.Enqueue(streamToEnqueue);

        // 重置现有流和写入器
        memStream.SetLength(0);
        memStream.Position = 0;
        writerMem.Dispose();
        writerMem = new WaveFileWriter(new IgnoreDisposeStream(memStream), new WaveFormat(44100, 16, 1));
        
        progressBar1.Value = 0;
    }
    else
    {
        progressBar1.Value = secondsRecorded;
    }
}

配套实现IgnoreDisposeStream包装类,防止WaveFileWriter.Dispose()销毁底层流:

public class IgnoreDisposeStream : Stream
{
    private readonly Stream _innerStream;

    public IgnoreDisposeStream(Stream innerStream) => _innerStream = innerStream;

    public override bool CanRead => _innerStream.CanRead;
    public override bool CanSeek => _innerStream.CanSeek;
    public override bool CanWrite => _innerStream.CanWrite;
    public override long Length => _innerStream.Length;
    public override long Position { get => _innerStream.Position; set => _innerStream.Position = value; }

    public override void Flush() => _innerStream.Flush();
    public override int Read(byte[] buffer, int offset, int count) => _innerStream.Read(buffer, offset, count);
    public override long Seek(long offset, SeekOrigin origin) => _innerStream.Seek(offset, origin);
    public override void SetLength(long value) => _innerStream.SetLength(value);
    public override void Write(byte[] buffer, int offset, int count) => _innerStream.Write(buffer, offset, count);

    // 重写Dispose,不释放内部流
    protected override void Dispose(bool disposing) { }
}

2. 预先计算10秒音频字节数,避免重复除法运算

提前算出10秒对应的字节总量,直接对比流长度,减少浮点运算开销:

// 初始化时计算一次
private readonly int _tenSecondByteCount = new WaveFormat(44100, 16, 1).AverageBytesPerSecond * 10;

// OnDataAvailable中替换判断逻辑
if (writerMem.Length >= _tenSecondByteCount)
{
    // 入队和重置逻辑...
}

3. 异步处理队列消费与API发送,不阻塞录音线程

把API发送操作放到后台任务中,避免在录音回调里同步处理阻塞数据写入:

private bool _isRecording = true;

// 初始化时启动后台消费任务
public YourClass()
{
    _ = Task.Run(async () =>
    {
        while (_isRecording)
        {
            if (memQ.TryDequeue(out var audioStream))
            {
                await SendAudioToApi(audioStream);
                audioStream.Dispose();
            }
            await Task.Delay(100);
        }
    });
}

// 停止录音时更新标记
public void StopRecording()
{
    _isRecording = false;
}

4. 减少UI线程占用,仅必要时更新UI

录音回调中仅更新进度条时切换到UI线程,其余逻辑留在录音线程执行:

void OnDataAvailable(object sender, WaveInEventArgs e)
{
    // 直接在录音线程处理写入和判断
    writerMem.Write(e.Buffer, 0, e.BytesRecorded);

    int secondsRecorded = (int)(writerMem.Length / writerMem.WaveFormat.AverageBytesPerSecond);

    if (secondsRecorded < 10)
    {
        UpdateProgressBar(secondsRecorded);
    }
    else
    {
        // 入队和重置逻辑...
        UpdateProgressBar(0);
    }
}

private void UpdateProgressBar(int value)
{
    if (progressBar1.InvokeRequired)
    {
        progressBar1.BeginInvoke(new Action(() => progressBar1.Value = value));
    }
    else
    {
        progressBar1.Value = value;
    }
}

内容的提问来源于stack exchange,提问作者Marcio Correa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 17:45:13