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

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;
}

验证步骤

  1. 优先修复DataAvailable事件的字节长度问题,这是导致处理端卡顿的直接原因
  2. 再添加缓冲区复用逻辑,解决GC频繁回收问题
  3. 最后验证缓冲区大小匹配的调整,确保录音线程与底层AudioRecord同步

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 01:12:03