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

