如何使用AudioKit 5.6导出/保存移调后的音频文件?
问题描述
我正在处理一个.m4a格式的音频文件,需要为其添加移调效果,现在希望将处理后的音频导出保存到本地,但不知如何操作。我尝试用renderToFile方法,但无法使其正常工作,以下是我目前的代码实现:
import AudioKit import SoundpipeAudioKit import AudioKitEX import AudioToolbox import AVFoundation struct TunerData { var pitch: Float = 0.0 var amplitude: Float = 0.0 var noteNameWithSharps = "-" var noteNameWithFlats = "-" } class Conductor { let engine = AudioEngine() var player: AudioPlayer! var pitchShifter: PitchShifter! var tracker: PitchTap! var mixer: Mixer! private var musicTrackPlayer: AVPlayer? var data = TunerData() let noteFrequencies = [16.35, 17.32, 18.35, 19.45, 20.6, 21.83, 23.12, 24.5, 25.96, 27.5, 29.14, 30.87] let noteNamesWithSharps = ["C", "C♯", "D", "D♯", "E", "F", "F♯", "G", "G♯", "A", "A♯", "B"] let noteNamesWithFlats = ["C", "D♭", "D", "E♭", "E", "F", "G♭", "G", "A♭", "A", "B♭", "B"] var pitch: Float = 0 init() { print("Initializing Conductor") do { player = AudioPlayer() player.isLooping = true pitchShifter = PitchShifter(player) mixer = Mixer(Fader(player), Fader(pitchShifter)) engine.output = mixer try engine.start() startFrequencyTracker() } catch { print("catch-error: ", error.localizedDescription) fatalError() } } } // MARK: - PitchTap extension Conductor { private func startFrequencyTracker() { tracker = PitchTap(pitchShifter) { pitch, amp in DispatchQueue.main.async { self.update(pitch[0], amp[0]) } } tracker.start() } private func initializeMusicTrackPlayer(url: URL) { musicTrackPlayer = .init(url: url) musicTrackPlayer?.play() } private func update(_ pitch: AUValue, _ amp: AUValue) { // Reduces sensitivity to background noise to prevent random / fluctuating data. guard amp > 0.1 else { data.pitch = 0 data.amplitude = 0 data.noteNameWithFlats = "-" data.noteNameWithSharps = "-" return } data.pitch = pitch data.amplitude = amp var frequency = pitch while frequency > Float(noteFrequencies[noteFrequencies.count - 1]) { frequency /= 2.0 } while frequency < Float(noteFrequencies[0]) { frequency *= 2.0 } var minDistance: Float = 10000.0 var index = 0 for possibleIndex in 0 ..< noteFrequencies.count { let distance = fabsf(Float(noteFrequencies[possibleIndex]) - frequency) if distance < minDistance { index = possibleIndex minDistance = distance } } let octave = Int(log2f(pitch / frequency)) data.noteNameWithSharps = "\(noteNamesWithSharps[index])\(octave)" data.noteNameWithFlats = "\(noteNamesWithFlats[index])\(octave)" print("pitch", data.pitch, "noteNameWithFlats", self.data.noteNameWithFlats, "frequency", frequency) let targetFrequency = Float(noteFrequencies[8]) let pitchShiftRatio = pitch / targetFrequency // Apply pitch shift // pitchShifter.shift = pitchShiftRatio } private func stopFrequencyTracker() { tracker.stop() } }
解决方案
要实现音频导出,你需要调整现有代码,补充音频加载、导出逻辑,同时处理引擎状态与文件权限:
1. 关键代码修改
给Conductor添加音频加载和导出的扩展方法:
extension Conductor { // 加载目标.m4a文件到AudioPlayer func loadAudioFile(from url: URL) throws { let audioFile = try AVAudioFile(forReading: url) player = AudioPlayer(file: audioFile) // 重建音频处理链 pitchShifter = PitchShifter(player) // 如果只需要导出移调后的音频,移除原音混音;需要混合则保留原mixer逻辑 mixer = Mixer(Fader(pitchShifter)) engine.output = mixer } // 导出处理后的音频到指定路径 func exportProcessedAudio(to outputURL: URL, completion: @escaping (Error?) -> Void) { guard let player = player else { completion(NSError(domain: "Conductor", code: -1, userInfo: [NSLocalizedDescriptionKey: "未加载音频文件"])) return } // 停止实时播放与音高追踪 tracker.stop() engine.stop() do { let audioFormat = player.file.processingFormat let audioDuration = player.file.length / audioFormat.sampleRate // 执行音频渲染 let renderedFile = try renderToFile(outputURL, duration: audioDuration, format: audioFormat, render: { player.play() // 等待播放完成以完成渲染 while player.isPlaying { usleep(10000) } }) print("导出完成:\(renderedFile.url.path)") completion(nil) } catch { print("导出失败:\(error.localizedDescription)") completion(error) } finally { // 可选:重启引擎恢复实时功能 try? engine.start() startFrequencyTracker() } } }
2. 使用示例
在业务逻辑中调用加载与导出方法:
let conductor = Conductor() // 替换为你的输入音频路径(示例为Bundle内文件) guard let inputURL = Bundle.main.url(forResource: "source", withExtension: "m4a") else { fatalError("未找到输入音频文件") } // 输出路径设为Documents目录 let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let outputURL = documentsDir.appendingPathComponent("shifted_audio.m4a") do { try conductor.loadAudioFile(from: inputURL) // 设置移调参数,示例为升1个全音(比例值对应半音数) conductor.pitchShifter.shift = 1.122462 // 执行导出 conductor.exportProcessedAudio(to: outputURL) { error in if let error = error { print("导出出错:\(error)") } else { print("文件已保存至:\(outputURL.path)") } } } catch { print("加载音频失败:\(error)") }
注意事项
- 确保App拥有磁盘写入权限,需在
Info.plist中添加NSDocumentsFolderUsageDescription说明用途。 - 若需要导出原音与移调音频的混合版本,保持原代码中
mixer = Mixer(Fader(player), Fader(pitchShifter))的逻辑即可。 renderToFile要求音频引擎处于停止状态,因此导出前需停止引擎,完成后可按需重启恢复实时功能。
内容的提问来源于stack exchange,提问作者Sotiris Kaniras
相关产品推荐
相关产品推荐

