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

使用Apple CoreAudio播放440Hz正弦波时音高异常及失声问题求助

CoreAudio实时渲染正弦波音高偏移及消失问题解决

问题根源

你的代码中使用**Float类型存储时间累加变量time**是核心问题。单精度Float的有效数字仅为6-7位,当time累加至128秒时,deltaTime(约2.26757e-5)的精度无法被Float准确表示,累加误差会逐渐放大:

  • 128秒后,误差导致相位计算偏慢,音高降低;
  • 256秒时误差累积到反向偏移,音高升高;
  • 512秒时,Float精度不足以容纳deltaTime的增量,time停止变化,正弦波变为固定值,声音完全消失。

修复方案

将时间相关变量改为Double类型,利用其15-17位有效数字的高精度,避免长时间累加的精度丢失。同时可以优化相位计算逻辑,直接基于帧计数计算当前相位,进一步规避累加误差。

修改后的代码

import Foundation
import AVFoundation

class Player {
    var audioUnit: AudioUnit?
    var audioEngine: AVAudioEngine = AVAudioEngine()
    var sampleRate: Double = 440.0
    // 改为Double类型存储时间和增量
    var time: Double = 0
    var deltaTime: Double = 0
    var mainMixer: AVAudioMixerNode?
    var outputNode: AVAudioOutputNode?
    var format: AVAudioFormat?
    var engine: Box?

    private lazy var sourceNode = AVAudioSourceNode { (_, _, frameCount, outputData) -> OSStatus in
        let ablPointer = UnsafeMutableAudioBufferListPointer(outputData)
        for frame in 0..<Int(frameCount) {
            // 使用Double计算相位,避免精度丢失
            let sampleVal = sin(440.0 * 2.0 * Double.pi * self.time)
            self.time += self.deltaTime
            for buffer in ablPointer {
                let buf: UnsafeMutableBufferPointer<Float> = UnsafeMutableBufferPointer(buffer)
                // 最后转成Float写入缓冲区即可
                buf[frame] = Float(sampleVal)
            }
        }
        return noErr
    }

    init(engine: inout Box) {
        mainMixer = audioEngine.mainMixerNode
        outputNode = audioEngine.outputNode
        format = outputNode!.inputFormat(forBus: 0)
        sampleRate = format!.sampleRate
        deltaTime = 1 / sampleRate
        
        self.engine = engine

        let inputFormat = AVAudioFormat(commonFormat: format!.commonFormat, sampleRate: sampleRate, channels: 1, interleaved: format!.isInterleaved)
        audioEngine.attach(sourceNode)
        audioEngine.connect(sourceNode, to: mainMixer!, format: inputFormat!)
        audioEngine.connect(mainMixer!, to: outputNode!, format: nil)
        mainMixer?.outputVolume = 1
    }

    func start() {
        do {
            try audioEngine.start()
        } catch {
            fatalError("Coud not start engine: \(error.localizedDescription)")
        }
    }

    func stop() {
        audioEngine.stop()
    }
}

额外优化建议

如果希望彻底避免累加误差,可以直接通过当前帧的偏移量计算相位,无需维护time变量:

// 替换sourceNode中的循环逻辑
private lazy var sourceNode = AVAudioSourceNode { (_, _, frameCount, outputData) -> OSStatus in
    let ablPointer = UnsafeMutableAudioBufferListPointer(outputData)
    // 维护一个全局帧计数(用UInt64避免溢出)
    static var totalFrames: UInt64 = 0
    for frame in 0..<Int(frameCount) {
        let currentFrame = totalFrames + UInt64(frame)
        let phase = 440.0 * 2.0 * Double.pi * Double(currentFrame) * self.deltaTime
        let sampleVal = sin(phase)
        for buffer in ablPointer {
            let buf: UnsafeMutableBufferPointer<Float> = UnsafeMutableBufferPointer(buffer)
            buf[frame] = Float(sampleVal)
        }
    }
    totalFrames += UInt64(frameCount)
    return noErr
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 15:03:17