Android平台NAudio自定义录音驱动PCM Float高缓冲卡顿问题求助
问题分析与解决方案
核心问题定位
自定义NAudio录音驱动在PCM Float格式下,大缓冲时长(40/50ms)出现卡顿,但16位PCM格式无此问题,主要由以下几个原因导致:
- 每次循环重复创建缓冲区,引发GC频繁回收:大缓冲时数组内存占用更高,频繁内存分配与回收会阻塞录音线程,导致音频卡顿
- DataAvailable事件传递错误的字节长度:当前传递全量缓冲区长度,但实际有效字节数应为读取的浮点样本数×4,无效数据会导致处理端逻辑异常卡顿
- 缓冲区大小计算不匹配:
RecordingLogic中重新计算的缓冲区大小,可能与AudioRecord初始化时的内部缓冲区大小不一致,大缓冲时更容易出现读取同步问题
针对性修复方案
方案1:复用缓冲区,避免频繁GC
将Float格式的缓冲区提前初始化,在循环中复用,减少内存分配次数:
private void RecordingLogic() { //Initialize the wave buffer int bufferSize = BufferMilliseconds * WaveFormat.AverageBytesPerSecond / 1000; if (bufferSize % WaveFormat.BlockAlign != 0) { bufferSize -= bufferSize % WaveFormat.BlockAlign; } WaveBuffer waveBuffer = new WaveBuffer(bufferSize); // 提前初始化Float格式缓冲区,循环内复用 float[] floatBuffer = null; byte[] byteBuffer = null; if (WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat) { floatBuffer = new float[bufferSize / 4]; byteBuffer = new byte[bufferSize]; } captureState = CaptureState.Capturing; //Run the record loop while (captureState != CaptureState.Stopped) { if (captureState != CaptureState.Capturing) { Thread.Sleep(10); continue; } if (WaveFormat.Encoding == WaveFormatEncoding.Pcm) { var bytesRead = audioRecord.Read(waveBuffer.ByteBuffer, 0, bufferSize); if (bytesRead > 0) { DataAvailable?.Invoke(this, new WaveInEventArgs(waveBuffer.ByteBuffer, bytesRead)); } } else if (WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat) { try { var floatsRead = audioRecord.Read(floatBuffer, 0, floatBuffer.Length, 1); if (floatsRead > 0) { Buffer.BlockCopy(floatBuffer, 0, byteBuffer, 0, floatsRead * 4); // 传递实际有效字节数,而非全量缓冲区大小 DataAvailable?.Invoke(this, new WaveInEventArgs(byteBuffer, floatsRead * 4)); } } catch(Exception ex) { Console.WriteLine(ex); } } } }
方案2:统一缓冲区大小计算逻辑
直接使用AudioRecord初始化时的缓冲区大小,避免读取时的同步问题:
private void RecordingLogic() { if (audioRecord == null) return; // 直接复用AudioRecord的缓冲区大小,确保读取逻辑与底层一致 int bufferSize = audioRecord.BufferSizeInFrames * WaveFormat.BlockAlign; WaveBuffer waveBuffer = new WaveBuffer(bufferSize); float[] floatBuffer = null; byte[] byteBuffer = null; if (WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat) { floatBuffer = new float[bufferSize / 4]; byteBuffer = new byte[bufferSize]; } captureState = CaptureState.Capturing; //Run the record loop while (captureState != CaptureState.Stopped) { if (captureState != CaptureState.Capturing) { Thread.Sleep(10); continue; } if (WaveFormat.Encoding == WaveFormatEncoding.Pcm) { var bytesRead = audioRecord.Read(waveBuffer.ByteBuffer, 0, bufferSize); if (bytesRead > 0) { DataAvailable?.Invoke(this, new WaveInEventArgs(waveBuffer.ByteBuffer, bytesRead)); } } else if (WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat) { try { var floatsRead = audioRecord.Read(floatBuffer, 0, floatBuffer.Length, 1); if (floatsRead > 0) { Buffer.BlockCopy(floatBuffer, 0, byteBuffer, 0, floatsRead * 4); DataAvailable?.Invoke(this, new WaveInEventArgs(byteBuffer, floatsRead * 4)); } } catch(Exception ex) { Console.WriteLine(ex); } } } }
方案3:调整读取模式,避免线程占用过高
将阻塞读取模式改为非阻塞模式,配合短暂休眠让出CPU:
// 将读取模式参数从1改为0(非阻塞) var floatsRead = audioRecord.Read(floatBuffer, 0, floatBuffer.Length, 0); if (floatsRead == 0) { Thread.Sleep(1); // 短暂休眠,避免线程空转占用过高CPU continue; }
验证步骤
- 优先修复DataAvailable事件的字节长度问题,这是导致处理端卡顿的直接原因
- 再添加缓冲区复用逻辑,解决GC频繁回收问题
- 最后验证缓冲区大小匹配的调整,确保录音线程与底层AudioRecord同步
内容的提问来源于stack exchange,提问作者SineVector241
相关产品推荐
相关产品推荐

