如何在Swift中实现非常精确的计时?

问题描述 投票:9回答:3

我正在开发一个具有琶音/排序功能的音乐应用程序,需要很高的计时准确性。目前,使用“Timer”我已经达到了平均抖动约为5ms的精度,但最大抖动约为11ms,这对于8号,16号和32号音符的快速琶音来说是不可接受的。

我已经读过'CADisplayLink'比'Timer'更准确,但由于它的准确度(~16-17ms)限制在1/60秒,看起来它的准确性不如我用Timer取得了成就。

潜入CoreAudio是实现我想要的唯一途径吗?还有其他方法可以实现更精确的计时吗?

ios swift timer core-audio timing
3个回答
5
投票

我在iPhone 7上对TimerDispatchSourceTimer(又名GCD计时器)进行了一些测试,测试时间为0.05秒,1000个数据点。我期待GCD计时器明显更准确(假设它有一个专用队列),但我发现它们是可比较的,我的各种试验的标准偏差范围从0.2-0.8毫秒,最大偏差平均值约为2 -8毫秒。

当我在mach_wait_until中尝试使用Technical Note TN2169: High Precision Timers in iOS / OS X时,我获得的计时器大约是我用Timer或GCD计时器获得的计数器的4倍。

话虽如此,我并不完全相信mach_wait_until是最好的方法,因为thread_policy_set的具体政策价值的确定似乎没有得到很好的记录。但是下面的代码反映了我在测试中使用的值,使用了从How to set realtime thread in Swift?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) {
        $0.withMemoryRebound(to: integer_t.self, capacity: Int(THREAD_TIME_CONSTRAINT_POLICY_COUNT)) {
            thread_policy_set(thread, UInt32(THREAD_TIME_CONSTRAINT_POLICY), $0, 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定时器更高的保真度,代价是What are the do's and dont's of code running with high precision timers?中描述的CPU /功耗

我很欣赏你对这一最后一点的怀疑,但我怀疑潜入CoreAudio并看看它是否可以提供更强大的解决方案是明智的。


3
投票

对于可接受的音乐准确节奏,唯一合适的定时源是使用Core Audio或AVFoundation。


0
投票

我自己正在开发一个音序器应用程序,我会擅自推荐将AudioKit用于这些目的。它有自己的音序器类。 https://audiokit.io/

© www.soinside.com 2019 - 2024. All rights reserved.