如何在 Swift 中实现非常准确的计时?
How do I achieve very accurate timing in Swift?
我正在开发一款具有 arpeggio/sequencing 功能的音乐应用,该功能需要很高的计时精度。目前,使用“定时器”我已经达到了平均抖动约 5 毫秒的精度,但最大抖动约 11 毫秒,这对于 8 分音符、16 分音符和 32 分音符的快速琶音来说是不可接受的。
我读过 'CADisplayLink' 比 'Timer' 更准确,但由于它的准确度限制为 1/60 秒(~16-17 毫秒),所以看起来与我用 Timer
.
取得的结果相比,这将是一种不太准确的方法
深入研究 CoreAudio 是否是实现我想要的唯一途径?有没有其他方法可以实现更准确的计时?
对于可接受的音乐准确节奏,唯一合适的定时源是使用 Core Audio 或 AVFoundation。
我在 iPhone 7 上用 1000 个数据点以 0.05 秒的间隔对 Timer
和 DispatchSourceTimer
(又名 GCD 计时器)进行了一些测试。我原以为 GCD 计时器会明显更准确(假设它有一个专用队列),但我发现它们具有可比性,我的各种试验的标准偏差范围为 0.2-0.8 毫秒,与平均值的最大偏差约为 2 -8 毫秒。
当按照 Technical Note TN2169: High Precision Timers in iOS / OS X 中所述尝试 mach_wait_until
时,我获得的计时器的准确度大约是我使用 Timer
或 GCD 计时器获得的精度的 4 倍。
话虽如此,我并不完全相信 mach_wait_until
是最好的方法,因为 thread_policy_set
的具体策略值的确定似乎没有很好的记录。但是下面的代码反映了我在测试中使用的值,使用的代码改编自 and TN2169:
var timebaseInfo = mach_timebase_info_data_t()
func configureThread() {
mach_timebase_info(&timebaseInfo)
let clock2abs = Double(timebaseInfo.denom) / Double(timebaseInfo.numer) * Double(NSEC_PER_SEC)
let period = UInt32(0.00 * clock2abs)
let computation = UInt32(0.03 * clock2abs) // 30 ms of work
let constraint = UInt32(0.05 * clock2abs)
let THREAD_TIME_CONSTRAINT_POLICY_COUNT = mach_msg_type_number_t(MemoryLayout<thread_time_constraint_policy>.size / MemoryLayout<integer_t>.size)
var policy = thread_time_constraint_policy()
var ret: Int32
let thread: thread_port_t = pthread_mach_thread_np(pthread_self())
policy.period = period
policy.computation = computation
policy.constraint = constraint
policy.preemptible = 0
ret = withUnsafeMutablePointer(to: &policy) {
[=10=].withMemoryRebound(to: integer_t.self, capacity: Int(THREAD_TIME_CONSTRAINT_POLICY_COUNT)) {
thread_policy_set(thread, UInt32(THREAD_TIME_CONSTRAINT_POLICY), [=10=], THREAD_TIME_CONSTRAINT_POLICY_COUNT)
}
}
if ret != KERN_SUCCESS {
mach_error("thread_policy_set:", ret)
exit(1)
}
}
然后我可以做:
private func nanosToAbs(_ nanos: UInt64) -> UInt64 {
return nanos * UInt64(timebaseInfo.denom) / UInt64(timebaseInfo.numer)
}
private func startMachTimer() {
Thread.detachNewThread {
autoreleasepool {
self.configureThread()
var when = mach_absolute_time()
for _ in 0 ..< maxCount {
when += self.nanosToAbs(UInt64(0.05 * Double(NSEC_PER_SEC)))
mach_wait_until(when)
// do something
}
}
}
}
请注意,您可能想看看 when
是否还没有过去(如果您无法在分配的时间内完成处理,您希望确保您的计时器不会积压) ,但希望这能说明这个想法。
无论如何,使用 mach_wait_until
,我实现了比 Timer
或 GCD 计时器更高的保真度,但代价是 CPU/power 消耗,如 What are the do's and dont's of code running with high precision timers? [=24= 中所述]
感谢您对最后一点的怀疑,但我怀疑深入研究 CoreAudio 并看看它是否可以提供更强大的解决方案是明智的。
我自己正在开发一个音序器应用程序,我强烈建议将 AudioKit 用于这些目的。
它有自己的音序器 class。
https://audiokit.io/
我正在开发一款具有 arpeggio/sequencing 功能的音乐应用,该功能需要很高的计时精度。目前,使用“定时器”我已经达到了平均抖动约 5 毫秒的精度,但最大抖动约 11 毫秒,这对于 8 分音符、16 分音符和 32 分音符的快速琶音来说是不可接受的。
我读过 'CADisplayLink' 比 'Timer' 更准确,但由于它的准确度限制为 1/60 秒(~16-17 毫秒),所以看起来与我用 Timer
.
深入研究 CoreAudio 是否是实现我想要的唯一途径?有没有其他方法可以实现更准确的计时?
对于可接受的音乐准确节奏,唯一合适的定时源是使用 Core Audio 或 AVFoundation。
我在 iPhone 7 上用 1000 个数据点以 0.05 秒的间隔对 Timer
和 DispatchSourceTimer
(又名 GCD 计时器)进行了一些测试。我原以为 GCD 计时器会明显更准确(假设它有一个专用队列),但我发现它们具有可比性,我的各种试验的标准偏差范围为 0.2-0.8 毫秒,与平均值的最大偏差约为 2 -8 毫秒。
当按照 Technical Note TN2169: High Precision Timers in iOS / OS X 中所述尝试 mach_wait_until
时,我获得的计时器的准确度大约是我使用 Timer
或 GCD 计时器获得的精度的 4 倍。
话虽如此,我并不完全相信 mach_wait_until
是最好的方法,因为 thread_policy_set
的具体策略值的确定似乎没有很好的记录。但是下面的代码反映了我在测试中使用的值,使用的代码改编自
var timebaseInfo = mach_timebase_info_data_t()
func configureThread() {
mach_timebase_info(&timebaseInfo)
let clock2abs = Double(timebaseInfo.denom) / Double(timebaseInfo.numer) * Double(NSEC_PER_SEC)
let period = UInt32(0.00 * clock2abs)
let computation = UInt32(0.03 * clock2abs) // 30 ms of work
let constraint = UInt32(0.05 * clock2abs)
let THREAD_TIME_CONSTRAINT_POLICY_COUNT = mach_msg_type_number_t(MemoryLayout<thread_time_constraint_policy>.size / MemoryLayout<integer_t>.size)
var policy = thread_time_constraint_policy()
var ret: Int32
let thread: thread_port_t = pthread_mach_thread_np(pthread_self())
policy.period = period
policy.computation = computation
policy.constraint = constraint
policy.preemptible = 0
ret = withUnsafeMutablePointer(to: &policy) {
[=10=].withMemoryRebound(to: integer_t.self, capacity: Int(THREAD_TIME_CONSTRAINT_POLICY_COUNT)) {
thread_policy_set(thread, UInt32(THREAD_TIME_CONSTRAINT_POLICY), [=10=], THREAD_TIME_CONSTRAINT_POLICY_COUNT)
}
}
if ret != KERN_SUCCESS {
mach_error("thread_policy_set:", ret)
exit(1)
}
}
然后我可以做:
private func nanosToAbs(_ nanos: UInt64) -> UInt64 {
return nanos * UInt64(timebaseInfo.denom) / UInt64(timebaseInfo.numer)
}
private func startMachTimer() {
Thread.detachNewThread {
autoreleasepool {
self.configureThread()
var when = mach_absolute_time()
for _ in 0 ..< maxCount {
when += self.nanosToAbs(UInt64(0.05 * Double(NSEC_PER_SEC)))
mach_wait_until(when)
// do something
}
}
}
}
请注意,您可能想看看 when
是否还没有过去(如果您无法在分配的时间内完成处理,您希望确保您的计时器不会积压) ,但希望这能说明这个想法。
无论如何,使用 mach_wait_until
,我实现了比 Timer
或 GCD 计时器更高的保真度,但代价是 CPU/power 消耗,如 What are the do's and dont's of code running with high precision timers? [=24= 中所述]
感谢您对最后一点的怀疑,但我怀疑深入研究 CoreAudio 并看看它是否可以提供更强大的解决方案是明智的。
我自己正在开发一个音序器应用程序,我强烈建议将 AudioKit 用于这些目的。 它有自己的音序器 class。 https://audiokit.io/