在iOS中准确计时

Ita*_*atz 16 time cocoa-touch ios4 ios

我正在查看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];    
}
Run Code Online (Sandbox Code Playgroud)

哪里

self.duration

在60 BPM的情况下是1.0秒.我想知道这个错误来自何处,以及如何制作更准确的计时器/间隔计数器.

编辑:当我将睡眠时间更改为较小的值时,问题也存在,例如.001.

EDIT2(更新):当我使用该CFAbsoluteTimeGetCurrent()方法进行计时时,问题也存在.当我使用相同的方法测量按钮点击事件之间的时间时,时间似乎准确 - 我每秒点击一次(在观看手表时),测量速率为60 BPM(平均值).所以我想它一定是NSThread(?)的一些问题.另一件事是在设备(iPod)上问题似乎比模拟器上更严重.

Ita*_*atz 21

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

我已经放置了一个变量来测量滴答之间的时间间隔,在play方法内部(实际将play消息发送到AVAudioPlayer对象的方法),并且正如我简单的比较外部观察实验所示,60 BPM太慢了 -我得到了这些时间间隔(以秒为单位):

1.004915
1.009982
1.010014
1.010013
1.010028
1.010105
1.010095
1.010105
Run Code Online (Sandbox Code Playgroud)

我的结论是在计算每个1秒间隔后经过了一些开销时间,并且在几十秒之后额外的时间(大约10毫秒)累积到显着的量 - 对于节拍器来说非常糟糕.因此,我没有测量呼叫之间的间隔,而是决定测量第一次呼叫的间隔,以便不会累积误差.换句话说,我已经取代了这个条件:

while (continuePlaying && ((currentTime0 + [duration doubleValue]) >= currentTime1)
Run Code Online (Sandbox Code Playgroud)

有这个条件:

while (continuePlaying && ((_currentTime0 + _cnt * [duration doubleValue]) >= currentTime1 ))
Run Code Online (Sandbox Code Playgroud)

现在在哪里_currentTime0以及_cnt是类成员(对不起,如果这是一个C++行话,我很新的对象- 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
Run Code Online (Sandbox Code Playgroud)

即使不计算平均值,这些值也很明显,这些值在1.0秒左右波动(平均值接近1.0,精度至少为毫秒).

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


Ale*_*fee 7

如果您需要<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;
}
Run Code Online (Sandbox Code Playgroud)

在我的测试中,该计时器每秒在0.0001秒内被一致地调用.

如果您在刷新率不同于60Hz的设备上运行,则必须分为displayLink.duration1.0并舍入到最接近的整数(不是最低值).