AudioKit音频播放器多轨道播放不同步问题求助
解决多音轨同步播放问题
你的问题出在逐个调用start()方法:每个AudioPlayer的启动都需要初始化音频缓冲区、与音频引擎同步,串行执行这些操作会导致音轨之间出现微小的启动延迟,16轨的情况下这个问题会更明显。DAW里的同步是因为所有音轨共享同一个播放时钟,而你的代码没有利用AudioKit的全局时钟来统一调度。
核心修复方案
- 统一音频路由:将所有音轨连接到同一个
Mixer,并把Mixer作为AudioEngine的输出,确保所有音轨共享同一音频时钟。 - 同步启动时机:使用音频引擎的当前时间作为所有音轨的启动时间戳,让它们在同一时刻开始播放。
- 批量管理音轨:用数组代替单独的变量,便于扩展到16轨,减少重复代码。
修改后的代码
MixerClass(优化版)
import Foundation import AudioKit import AVFoundation final class MixerClass: ObservableObject { let engine = AudioEngine() let mixer = Mixer() // 用数组管理所有音轨,便于扩展到16轨 @Published var tracks: [AudioPlayer] = [] // 统一管理播放状态,因为所有音轨同步启停 @Published var isPlaying: Bool = false private let trackNames = ["DQ_Kick", "DQ_Snare", "DQ_Tom1"] // 后续可扩展到16个 init() { do { Settings.bufferLength = .medium try AVAudioSession.sharedInstance().setPreferredIOBufferDuration(Settings.bufferLength.duration) try AVAudioSession.sharedInstance().setCategory(.playAndRecord, options: [.defaultToSpeaker, .mixWithOthers, .allowBluetoothA2DP]) try AVAudioSession.sharedInstance().setActive(true) } catch let err { print(err) } do { // 批量加载音轨 for name in trackNames { if let url = Bundle.main.url(forResource: name, withExtension: "mp3") { let player = AudioPlayer() try player.load(url: url) player.isLooping = true player.volume = 0.5 // 默认音量 tracks.append(player) // 将音轨连接到混音器 mixer.addInput(player) } else { Log("Could not find track: \(name)") } } // 将混音器设置为音频引擎的输出 engine.output = mixer // 启动音频引擎(提前启动,避免播放时的延迟) try engine.start() } catch let err { print(err) } } func playStopToggle() { guard !tracks.isEmpty else { return } if isPlaying { // 同步停止所有音轨 tracks.forEach { $0.stop() } } else { // 获取音频引擎的当前时间,作为所有音轨的启动时间戳 let startTime = engine.outputNode.lastRenderTime?.sampleTime ?? 0 let time = AVAudioTime(sampleTime: startTime, atRate: engine.outputNode.outputFormat(forBus: 0).sampleRate) // 所有音轨在同一时间点启动 tracks.forEach { $0.play(at: time) } } isPlaying.toggle() } }
FreePlayMain(适配修改)
import SwiftUI struct FreePlayMain: View { @EnvironmentObject var conductor: MixerClass var body: some View { NavigationStack { VStack { VStack { // 批量生成音量滑块 ForEach(Array(conductor.tracks.enumerated()), id: \.offset) { index, track in Slider(value: $conductor.tracks[index].volume, in: 0...1) .padding() .accessibilityLabel("Track \(index + 1) Volume") } } playStopButton } } } private var playStopButton: some View { Button(action: { conductor.playStopToggle() }) { VStack { Text("\(Image(systemName: conductor.isPlaying ? "stop.fill" : "play.fill")) \(conductor.isPlaying ? "Stop All" : "Start All")") } } } }
关键修改说明
- 音频路由连接:所有
AudioPlayer都添加到Mixer,Mixer作为AudioEngine的输出,确保所有音轨共享同一音频时钟,这是同步的基础。 - 同步启动:使用
engine.outputNode.lastRenderTime获取当前音频引擎的时间戳,调用play(at:)让所有音轨在同一时刻开始播放,彻底消除启动延迟。 - 批量管理:用数组存储音轨,后续扩展到16轨只需在
trackNames数组中添加文件名即可,无需重复编写加载和控制代码。 - 提前启动引擎:在
init中启动AudioEngine,避免首次播放时的引擎启动延迟。
内容的提问来源于stack exchange,提问作者Wpitchy
相关产品推荐
相关产品推荐

