我正在看来自iOS软件开发工具包(http://developer.apple.com/library/ios/#samplecode/Metronome/Introduction/Intro.html)的“节拍器”示例代码。我正在以60 BPM的速度运行节拍器,这意味着每秒钟有一个滴答声。当我看一个外部手表(PC的手表)时,我发现节拍器运行得太慢了-它每分钟错过一个节拍,这就是应用程序。15毫秒的一致错误。相关的代码片段是:
- (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
在60 BPM的情况下是1.0秒。我想知道这个错误是从哪里来的,以及如何制作更准确的计时器/间隔计数器。
编辑:当我将睡眠时间更改为较小的值时,问题也存在,例如.001。
EDIT2 (update):当我使用CFAbsoluteTimeGetCurrent()方法计时时,这个问题也存在。当我使用相同的方法来测量按钮点击事件之间的计时时,计时似乎是准确的-我每秒点击一次(在观看手表时),测得的速率是60 BPM (平均)。所以我猜一定是NSThread (?)有问题。另一件事是,设备(iPod)上的问题似乎比模拟器上的问题更严重。
发布于 2010-12-21 04:03:58
好了,在做了更多的测试后,我有了一些答案,所以我把它分享给任何感兴趣的人。
我在play方法(实际将play消息发送到AVAudioPlayer对象的方法)中放置了一个变量来测量滴答之间的时间间隔,并且正如我的简单的外部监视比较实验所显示的那样,60BPM太慢了-我得到了以下时间间隔(以秒为单位):
1.004915
1.009982
1.010014
1.010013
1.010028
1.010105
1.010095
1.010105我的结论是,在计算每1秒的间隔后,会经过一些开销时间,并且在几十秒之后,额外的时间(大约10毫秒)会累积到一个明显的量-对于节拍器来说,这是相当糟糕的。因此,我决定不测量调用之间的间隔,而是测量第一个调用的总间隔,这样就不会累积误差。换句话说,我已经替换了这个条件:
while (continuePlaying && ((currentTime0 + [duration doubleValue]) >= currentTime1)在这种情况下:
while (continuePlaying && ((_currentTime0 + _cnt * [duration doubleValue]) >= currentTime1 ))其中现在_currentTime0和_cnt是类成员(对不起,如果它是c++行话,我对Obj-C很陌生),前者保存第一次调用方法的时间戳,而后者是int计数节拍(==function调用)的时间戳。这导致了以下测量的时间间隔:
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,精度至少为1毫秒)。
我很高兴听到更多关于额外时间流逝的见解--10毫秒的声音对于现代的中央处理器来说是永恒的--尽管我不熟悉iPod处理器的规格(它是iPod 4G,维基百科说iPod是PowerVR SGX GPU535@ 200 MHz)
发布于 2013-04-27 10:59:39
如果您需要<100毫秒的精度,请查看CADisplayLink。它以固定的时间间隔调用选择器,速度高达每秒60次,即每.0166667秒。
如果您想要进行节拍器测试,可以将frameInterval设置为60,这样它就会每秒被回调一次。
self.syncTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(syncFired:)];
self.syncTimer.frameInterval = 60;
[self.syncTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
-(void)syncFired:(CADisplayLink *)displayLink
{
static NSTimeInterval lastSync = 0;
NSTimeInterval now = CACurrentMediaTime();
if (lastSync > 0) {
NSLog(@"interval: %f", now - lastSync);
}
lastSync = now;
}在我的测试中,该计时器在0.0001秒内每秒都会被一致地调用。
如果你在一个刷新率不同于60 the的设备上运行,你必须将displayLink.duration除以1.0并四舍五入到最接近的整数(而不是下限)。
发布于 2011-08-21 23:45:53
我对此也很感兴趣,也注意到了使用NSTimer和节拍器示例的谬误。
我目前正在研究CAMediaTiming Protocol...yet,我不知道如何使用它,但它可能会被证明是一个更精确的解决方案,因为它用于动画。我也可能完全错了,因为当太多的事情发生时,游戏就会变慢。我的理论基于我最喜欢的游戏,当“在街上与对手战斗”时,需要精确的时机。我认为这款游戏的fps已经降到了30,而游戏机的fps只有60。然而,游戏的组合系统的时机是非常关键的,例如,组合包括点击一个按钮,然后恰好3帧后的另一个,所以要么:
有关动画计时的信息可在此处找到:http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Animation_Types_Timing/Articles/Timing.html#//apple_ref/doc/uid/TP40006670-SW1
https://stackoverflow.com/questions/4485072
复制相似问题