问题1
我的第一个问题是将AVAudioPlayerNode
和AVAudioSequencer
用于MIDI时的播放同步。基本上,我试图通过MIDI播放某些东西,但是它们需要完全同步。
我知道AVAudioPlayerNode
有同步方法,但是音序器似乎没有这样的东西。
目前,我已经尝试在单独的线程上使用CAMediaTime() + delay
和usleep
,但是它们似乎不能很好地工作。
问题2 :我正在使用engine.inputNode
上的水龙头来获取录音,与音乐播放分开。但是,录音似乎开始得较早。当我将记录的数据与原始播放进行比较时,相差大约300毫秒。我可以在300毫秒后开始记录,但是即使那样,也不能保证精确的同步,并且可能与机器有关。
所以我的问题是,什么是确保在播放开始时准确开始录制的好方法?
答案 0 :(得分:4)
要同步音频io,通常最好创建一个参考时间,然后将此时间用于所有与时序相关的计算。
AVAudioPlayerNode.play(at:)是播放器所需要的。对于分接头,您需要使用闭包中提供的时间手动过滤(部分)缓冲区。不幸的是,AVAudioSequencer没有在特定时间启动的功能,但是您可以使用hostTime(forBeats)使用已经播放的音序器获得与节拍相关的参考时间。如果我没记错的话,您不能将音序器设置为负位置,因此这不是理想的选择。
这是一个骇人的解决方法,应该会产生非常准确的结果:
AVAudioSequencer必须在获取参考时间之前启动,将所有Midi数据偏移1,启动音序器,然后立即获取与拍子1相关的参考时间,然后将播放器的启动与此时间同步,并还可以使用它过滤掉水龙头捕获的有害音频。
func syncStart() throws {
//setup
sequencer.currentPositionInBeats = 0
player.scheduleFile(myFile, at: nil)
player.prepare(withFrameCount: 4096)
// Start and get reference time of beat 1
try sequencer.start()
// Wait until first render cycle completes or hostTime(forBeats) will err - AVAudioSequencer is fragile :/
while (self.sequencer.currentPositionInBeats <= 0) { usleep(UInt32(0.001 * 1000000.0)) }
var nsError: NSError?
let hostTime = sequencer.hostTime(forBeats: 1, error: &nsError)
let referenceTime = AVAudioTime(hostTime: hostTime)
// AVAudioPlayer is great for this.
player.play(at: referenceTime)
// This just rejects buffers that come too soon. To do this right you need to record partial buffers.
engine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: nil) { (buffer, audioTime) in
guard audioTime.hostTime >= referenceTime.hostTime else { return }
self.recordBuffer(buffer: buffer)
}
}
答案 1 :(得分:-1)
dave234's answer对我不起作用,因为hostTime(forBeats:error:)
甚至在先启动音序器后仍然崩溃。 (当我延迟一段时间后异步调度时,它确实起作用了,但这会导致进一步的复杂化)。但是,它提供了对同步方法的宝贵见解,这就是我所做的:
var refTime: AVAudioTime
if isMIDIPlayer {
sequencer!.tracks.forEach { $0.offsetTime = 1 }
sequencer!.currentPositionInBeats = 0
let sec = sequencer!.seconds(forBeats: 1)
let delta = AVAudioTime.hostTime(forSeconds: sec) + mach_absolute_time()
refTime = AVAudioTime(hostTime: delta)
try sequencer!.start()
} else {
player!.prepare(withFrameCount: 4096)
let delta = AVAudioTime.hostTime(forSeconds: 0.5) + mach_absolute_time()
refTime = AVAudioTime(hostTime: delta)
player!.play(at: refTime)
}
mixer.installTap(
onBus: 0,
bufferSize: 8,
format: mixer.outputFormat(forBus: 0)
) { [weak self] (buffer, time) in
guard let strongSelf = self else { return }
guard time.hostTime >= refTime.hostTime else { print("NOPE"); return }
do {
try strongSelf.recordFile!.write(from: buffer)
} catch {
// TODO: Handle error
print(error)
}
}
有关代码段的一些解释:
AudioPlayer
,并且代码来自AudioPlayer
内部的方法。sequencer
用于MIDI播放。player
用于其他歌曲文件。MIDI播放同步使用类似的方法,如下所示:
midi!.sequencer!.tracks.forEach { $0.offsetTime = 1 }
let sec = midi!.sequencer!.seconds(forBeats: 1)
let delta = AVAudioTime.hostTime(forSeconds: sec) + mach_absolute_time()
let refTime = AVAudioTime(hostTime: delta)
do {
try midi!.play()
} catch {
// TODO: Add error handler
print(error)
}
song2.playAtTime(refTime)
在这里,midi
是AVAudioSequencer
对象,而song2
是播放常规歌曲的AVAudioPlayerNode
。
像魅力一样工作!