在iOS中准确计时

时间:2010-12-19 21:01:55

标签: cocoa-touch ios time ios4

我正在查看iOS SDK中的“Metronome”示例代码(http://developer.apple.com/library/ios/#samplecode/Metronome/Introduction/Intro.html)。我正在以60 BPM的速度运行节拍器,这意味着每秒都会打勾。当我看一下外部手表(PC的手表)时,我看到节拍器运行速度太慢 - 它错过了每分钟一次,这就是app。 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()方法进行计时时,问题也存在。当我使用相同的方法测量按钮点击事件之间的时间时,时间似乎准确 - 我每秒点击一次(在观看手表时),测量的速率为60 BPM(平均值)。所以我想这对NSThread(?)来说一定是个问题。另一件事是在设备(iPod)上,问题似乎比模拟器上更严重。

7 个答案:

答案 0 :(得分:21)

好的,我在做了一些测试之后得到了一些答案,所以我和任何感兴趣的人分享。

我已经在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,精度至少为毫秒)。

我很高兴听到更多关于导致额外时间的原因 - 现代CPU的10毫秒声音是永恒的 - 尽管我不熟悉iPod CPU的规格(它是iPod 4G,维基百科说的CUP是PowerVR SGX GPU 535 @ 200 MHz)

答案 1 :(得分:6)

如果您需要<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秒内被一致地调用。

如果您在刷新率不同于60Hz的设备上运行时,您必须将displayLink.duration除以1.0并舍入到最接近的整数(不是最低值)

答案 2 :(得分:3)

我对此也很感兴趣,也注意到使用NSTimer和节拍器示例的谬误。

我目前正在研究CAMediaTiming Protocol ......但我不知道如何使用它,但它可以证明是一个更精确的解决方案,因为它用于动画。我也可能完全错了,因为当太多时候游戏减速。我将我的理论建立在我最喜欢的游戏上,当“在街头对抗对手”时,需要精确的计时。我认为游戏的fps已降至30,而其控制台的时速为60.然而,游戏的组合系统的时机非常重要,例如:一个组合包括按一个按钮,然后是另一个正好3帧之后,所以:

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

动画时间信息可在此处找到:http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Animation_Types_Timing/Articles/Timing.html#//apple_ref/doc/uid/TP40006670-SW1

答案 3 :(得分:1)

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

答案 4 :(得分:1)

使用while循环和睡眠时间节拍器不是解决这个问题的有效方法,因为它可能产生抖动和漂移的时间,如您所见。我相信解决这个问题的标准方法是使用Core Audio(以这种或那种方式)提供连续音频流,其中包含节拍器节拍,它们之间的正确静音量相隔,具体取决于节奏。因为您知道确切的采样率,所以您可以非常准确地计算时间。不幸的是,自己生成音频比你尝试做的要困难得多,但this Stackoverflow question可能会让你开始。

答案 5 :(得分:0)

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

您的建议完全符合我的需求 - 您是否设法提高准确度?

答案 6 :(得分:0)

您是否考虑使用NSTimer而不是这种“奇怪的”while-loop解决方案?在我看来,你 - 不仅仅是 - 在这里过于复杂的非常简单的事情......