我正在查看 iOS SDK 中的Metronome"示例代码 (http://developer.apple.com/library/ios/#samplecode/Metronome/Introduction/Intro.html).我以 60 BPM 的速度运行节拍器,这意味着每秒一个滴答声.当我查看外接手表(PC 的手表)时,我发现节拍器运行速度太慢 - 它每分钟错过一个节拍,这就是 app.15 毫秒的一致错误.相关的代码片段是:
I am looking at the 'Metronome' sample code from the iOS SDK (http://developer.apple.com/library/ios/#samplecode/Metronome/Introduction/Intro.html). I am running the metronome at 60 BPM, which means a tick every second. When I look at an external watch (the PC's watch), I see the metronome is running too slow - it misses about one beat each minute, which is app. 15msec of consistent error. The relevant code piece is:
- (void)startDriverTimer:(id)info {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Give the sound thread high priority to keep the timing steady.
[NSThread setThreadPriority:1.0];
BOOL continuePlaying = YES;
while (continuePlaying) { // Loop until cancelled.
// An autorelease pool to prevent the build-up of temporary objects.
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
[self playSound];
[self performSelectorOnMainThread:@selector(animateArmToOppositeExtreme) withObject:nil waitUntilDone:NO];
NSDate *curtainTime = [[NSDate alloc] initWithTimeIntervalSinceNow:self.duration];
NSDate *currentTime = [[NSDate alloc] init];
// Wake up periodically to see if we've been cancelled.
while (continuePlaying && ([currentTime compare:curtainTime] != NSOrderedDescending)) {
if ([soundPlayerThread isCancelled] == YES) {
continuePlaying = NO;
}
[NSThread sleepForTimeInterval:0.01];
[currentTime release];
currentTime = [[NSDate alloc] init];
}
[curtainTime release];
[currentTime release];
[loopPool drain];
}
[pool drain];
}
在哪里
self.duration
self.duration
在 60 BPM 的情况下为 1.0 秒.我想知道这个错误来自哪里,以及如何制作更准确的计时器/间隔计数器.
is 1.0 second in the case of 60 BPM. I wonder where this error comes from, and how can I make a more accurate timer/interval counter.
当我将睡眠时间更改为较小的值时,问题也存在,例如 .001.
The problem exists as well when I change the sleep time to smaller values, e.g .001.
EDIT2(更新):当我使用 CFAbsoluteTimeGetCurrent()
方法进行计时时,问题也存在.当我使用相同的方法测量按钮点击事件之间的时间时,时间似乎准确 - 我每秒点击一次(在观看手表时),测量的速率为 60 BPM(平均).所以我想这一定是 NSThread
(?) 的问题.另一件事是,在设备 (iPod) 上,问题似乎比在模拟器上更严重.
EDIT2 (update): The problem exists as well when I use the CFAbsoluteTimeGetCurrent()
method for timing. When I use the same method to measure timing between a button tap events, the timing seems accurate - I tap once a second (while watching a watch), and the measured rate is 60 BPM (on average). So I guess it must be some issue with the NSThread
(?). Another thing is that on the device (iPod) the problem seems more severe then on the simulator.
好的,我做了更多的测试后有了一些答案,所以我把它分享给任何有兴趣的人.
Ok, I have some answers after doing some more tests, so I am sharing it with anyone who is interested.
我在 play
方法(实际上将 play
消息发送到 AVAudioPlayer
对象),正如我简单的与外部手表比较实验所示,60 BPM 太慢了——我得到了这些时间间隔(以秒为单位):
I've placed a variable to measure time intervals between ticks, inside the play
method (the method that actually sends the play
message to the AVAudioPlayer
object), and as my simple compare-to-external-watch experiment showed, the 60 BPM was too slow - I got these time intervals (in seconds):
1.004915
1.009982
1.010014
1.010013
1.010028
1.010105
1.010095
1.010105
我的结论是,在计算每 1 秒间隔后会经过一些开销时间,而额外时间(大约 10 毫秒)会在几十秒后累积到明显的数量——对于节拍器来说非常糟糕.因此,我决定测量第一次调用的 total 间隔,而不是测量 between 调用的间隔,这样就不会累积错误.换句话说,我已经替换了这个条件:
My conclusion was that some overhead time elapses after each 1-second-interval is counted, and that extra time (about 10msec) is accumulated to a noticeable amount after a few tens of seconds --- quite bad for a metronome. So instead of measuring the interval between calls, I decided to measure the total interval from the first call, so that the error won't be accumulated. In other words I've replaced this condition:
while (continuePlaying && ((currentTime0 + [duration doubleValue]) >= currentTime1)
在这种情况下:
while (continuePlaying && ((_currentTime0 + _cnt * [duration doubleValue]) >= currentTime1 ))
现在 _currentTime0
和 _cnt
是类成员(对不起,如果它是一个 c++ 行话,我对 Obj-C 还很陌生),前者的时间戳为第一次调用该方法,后者是一个 int
计数滴答数(==函数调用).这导致了以下测量的时间间隔:
where now _currentTime0
and _cnt
are class members (sorry if it's a c++ jargon, I am quite new to Obj-C), the former holds the time stamp of the first call to the method, and the latter is an int
counting number of ticks (==function calls). This resulted in the following measured time intervals:
1.003942
0.999754
0.999959
1.000213
0.999974
0.999451
1.000581
0.999470
1.000370
0.999723
1.000244
1.000222
0.999869
很明显,即使不计算平均值,这些值也会在 1.0 秒左右波动(平均值接近 1.0,精度至少为一毫秒).
and it is evident even without calculating the average, that these values fluctuate around 1.0 second (and the average is close to 1.0 with at least a millisecond of accuracy).
我很高兴听到更多关于导致额外时间流逝的见解 - 10 毫秒对于现代 CPU 来说听起来是永恒的 - 尽管我不熟悉 iPod CPU 的规格(它是 iPod 4G,维基百科说CUP 是 PowerVR SGX GPU 535 @ 200 MHz)
I will be happy to hear more insights regarding what causes the extra time to elapse - 10msec sounds as eternity for a modern CPU - though I am not familiar with the specs of the iPod CPU (it's iPod 4G, and Wikipedia says the CUP is PowerVR SGX GPU 535 @ 200 MHz)
这篇关于iOS 中的精确计时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!