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

AVAudioEngine多通道转单通道及格式转换异常问题求助

问题分析与代码修复建议

我帮你排查出代码里几个关键问题,正好对应你遇到的三个异常情况,下面逐个拆解并给出修复方案:

1. 96k采样率下仅提取单侧通道信号

这个问题出在MicSinkNode对多通道音频的错误处理,以及WAV文件写入的字节序错误:

  • 当音频接口是立体声格式时,audioBufferList会包含左右两个声道的Buffer,但你的代码只读取了第一个Buffer的数据,自然只能拿到单侧信号;如果是交错格式的立体声,还会出现“间隔采样”的诡异效果。
  • WAV文件写入时设置了AVLinearPCMIsBigEndianKey: true,但标准WAV采用小端字节序,这会导致DAW解析时出现通道错误,加重异常表现。

修复代码:

首先修正WAV写入的字节序:

let outputFormatSettings = [
    AVFormatIDKey: kAudioFormatLinearPCM,
    AVLinearPCMBitDepthKey: 32,
    AVLinearPCMIsFloatKey: true,
    AVLinearPCMIsBigEndianKey: false, // 改为小端字节序
    AVSampleRateKey: SAMPLE_RATE,
    AVNumberOfChannelsKey: 1
] as [String : Any]

然后修正MicSinkNode的多通道处理(兼容formatMixer转换失效的情况,手动混合多通道到单声道):

let MicSinkNode = AVAudioSinkNode() { (timeStamp, frames, audioBufferList) -> OSStatus in
    if self.in1firstRun == true {
        let start1 = CACurrentMediaTime()
        self.in1StartTime = Double(start1)
        self.in1firstRun = false
    }

    // 提前判断是否停止录制
    if !self.running {
        return noErr
    }

    let abl = UnsafeMutableAudioBufferListPointer(audioBufferList)
    let frameCount = Int(frames)
    var monoSamples = [Float](repeating: 0, count: frameCount)

    // 手动混合所有通道到单声道
    for buffer in abl {
        let bufPtr = UnsafeMutableBufferPointer<Float>(buffer)
        for i in 0..<frameCount {
            monoSamples[i] += bufPtr[i] / Float(abl.count)
        }
    }

    // 检查缓冲区是否溢出
    if self.buf1current + frameCount >= self.in1Buf.count {
        self.running = false
        return noErr
    }

    // 用memcpy替代循环拷贝,提升效率
    memcpy(&self.in1Buf[self.buf1current], monoSamples, frameCount * MemoryLayout<Float>.stride)
    self.buf1current += frameCount

    return noErr
}

2. 同采样率下录制时长过长

原因是刺激声播放结束后,引擎仍在录制静音数据:

  • 你的sourceNode在播放完刺激声后会持续输出0,但MicSinkNode的停止条件是buf1current >=100000,这会额外录制大量静音。
  • running变量的同步逻辑不一致,sourceNode设置running=false后,MicSinkNode没有及时响应停止。

修复代码:

在sourceNode中,播放完刺激声后主动停止引擎(注意在非音频线程操作引擎):

if Int(frameCount) + time >= inputSweep.stimulus.count{
    self.running = false
    print("AUDIO ENGINE STOPPED")
    DispatchQueue.main.async {
        self.engine.stop()
    }
}

3. 44.1k转48k时出现卡顿

这个问题源于采样率转换的配置缺失和音频线程的低效操作:

  • 你请求了应用采样率,但没有检查系统是否接受,当接口固定为44.1k时,引擎会强制进行采样率转换,若缓冲区配置不合理就会卡顿。
  • MicSinkNode中的数组append和循环拷贝效率低下,拖慢音频线程。

修复代码:

首先完善AVAudioSession的配置,添加缓冲区时长设置,并检查实际采样率:

do {
    try AVAudioSession.sharedInstance().setCategory(.playAndRecord)
    // 设置10ms缓冲区,平衡延迟和稳定性
    try AVAudioSession.sharedInstance().setPreferredIOBufferDuration(0.01)
    // 请求采样率并获取实际生效的值
    try AVAudioSession.sharedInstance().setPreferredSampleRate(Double(sampleRate))
    print("实际生效采样率:\(AVAudioSession.sharedInstance().sampleRate)")
} catch {
    assertionFailure("AVAudioSession setup failed: \(error.localizedDescription)")
}

其他冗余/风险点修复

  • 移除引擎初始化时的冗余代码:不需要重新实例化engine,reset()已足够重置状态:
// 替换原来的stop/reset/reinit逻辑
engine.stop()
engine.reset()
// 删掉 engine = AVAudioEngine()
  • 不要用try! engine.start(),改用do-catch捕获错误避免崩溃:
do {
    try engine.start()
} catch {
    assertionFailure("引擎启动失败:\(error.localizedDescription)")
    running = false
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:45:43