准确的iOS时间

我正在查看iOS SDK中的“节拍器”示例代码( 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(更新):当我使用CFAbsoluteTimeGetCurrent()方法进行计时时也存在问题。 当我使用相同的方法来衡量一个button的点击事件之间的时间,时间似乎是准确的 – 我每秒敲一下(一边看手表),测量的速度是60 BPM(平均)。 所以我想这个问题肯定是NSThread (?)的问题。 另一件事是,在设备(iPod)上,问题似乎更严重,然后在模拟器上。

好的,我做了一些更多的testing后有一些答案,所以我将它分享给任何有兴趣的人。

我已经放置了一个variables来测量play方法(实际发送play消息到AVAudioPlayer对象的方法)内的节拍之间的时间间隔,而且由于我的简单的“比较对外部节拍”实验显示,60 BPM是太慢了 – 我得到了这些时间间隔(以秒为单位):

 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计数的蜱(==函数调用)。 这导致了以下测量的时间间隔:

 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,精度至less为1毫秒)。

我会很高兴听到更多关于什么导致额外的时间stream逝的见解–10毫秒听起来像一个现代CPU的永恒 – 虽然我不熟悉的iPod CPU的规格(这是iPod的4G,维基百科说,银联是PowerVR SGX GPU 535 @ 200 MHz)

如果您需要<100毫秒精度,请查看CADisplayLink 。 它定期调用一个select器,每秒最多60次,即每个.0166667秒。

如果你想做你的节拍器testing,你可以将frameInterval设置为60,这样frameInterval会调用一次。

 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; } 

在我的testing中,这个计时器在0.0001秒内每秒钟一直调用。

如果你在刷新频率不超过60Hz的设备上运行,你必须将displayLink.duration分成1.0和四舍五入到最接近的整数(不是地板)。

我也非常感兴趣,也注意到使用NSTimer和节拍器的例子的谬误。

我目前正在研究CAMediaTiming协议…但我不知道如何使用它,但它可以certificate是一个更精确的解决scheme,因为它用于animation。 如果游戏过多,游戏速度会变慢,我也可能完全错误。 我把我的理论build立在我最喜欢的游戏上,在“与街上的对手对抗”时需要准确的时间。 游戏的fps已经减less到了30,我认为相比于时钟为60的游戏机。但是游戏的组合系统的时间是非常关键的,例如,一个组合包括击中一个button,然后是另一个正好3帧后,所以:

  • iOS上的游戏在时间上更加宽容
  • 开发人员实施了自己的计时系统
  • 否则他们正在使用节拍器的计时器循环或CAMediaTiming协议。

有关animation计时的信息可以在这里find: http : //developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Animation_Types_Timing/Articles/Timing.html#//apple_ref/doc/uid/TP40006670-SW1

问题更为根本。 IOS不是一个实时的操作系统,所以做各种操作的时间不是“确定的”。 它有所不同。 为了准确,您需要一个像RIM的新QNX操作系统这样的实时操作系统。

使用一个while循环和睡眠时间节拍器是不是一个强大的方法来解决这个问题,因为它可能会产生时间紧张,漂移,如你所见。 我相信解决这个问题的标准方法是使用核心audio(以某种方式)来提供一个连续的audiostream,包含你的节拍器刻度,由它们之间的正确数量的静音隔开,这取决于节奏。 因为您知道确切的采样率,所以您可以非常准确地计时。 不幸的是,自己生成audio比你想要做的要困难得多,但是这个Stackoverflow问题可能会让你开始。

谢谢 ! 花了我一点时间来想出使用CFAbsoluteTimeGetCurrent()currentTime0和currentTime1,并且必须单独施放持续时间(CGFloat)(双倍myDuration =(双倍)持续时间),因为有母马尝试使用doubleValue。

你的build议完全适合我的需求 – 你有没有设法提高准确性?

你有没有考虑使用NSTimer而不是这个“奇怪”的while循环解决scheme? 在我看来,你是 – 不仅有点 – 在这里过于复杂的非常简单的事情…