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

