Точное время в iOS - PullRequest
       14

Точное время в iOS

16 голосов
/ 20 декабря 2010

Я смотрю пример кода «Метроном» из iOS SDK (http://developer.apple.com/library/ios/#samplecode/Metronome/Introduction/Intro.html). Я запускаю метроном со скоростью 60 ударов в минуту, что означает тик каждую секунду. Когда я смотрю на внешние часы (ПК смотрите), я вижу, что метроном работает слишком медленно - он пропускает около одного удара в минуту, что составляет примерно 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

составляет 1,0 секунды в случае 60 ударов в минуту. Интересно, откуда эта ошибка и как мне сделать более точный таймер / счетчик интервалов.

РЕДАКТИРОВАТЬ: проблема также существует, когда я изменяю время сна на меньшие значения, например .001.

EDIT2 (обновление): проблема также существует, когда я использую метод CFAbsoluteTimeGetCurrent() для синхронизации. Когда я использую тот же метод для измерения времени между событиями нажатия кнопки, время кажется точным - я нажимаю один раз в секунду (при просмотре часов), и измеренная скорость составляет 60 ударов в минуту (в среднем). Так что я думаю, что это может быть проблема с NSThread (?). Другое дело, что на устройстве (iPod) проблема кажется более серьезной, чем на симуляторе.

Ответы [ 7 ]

21 голосов
/ 20 декабря 2010

Хорошо, у меня есть несколько ответов после еще нескольких тестов, поэтому я делюсь ими с любыми заинтересованными.

Я поместил переменную для измерения временных интервалов между тиками внутри playmethod (метод, который фактически отправляет сообщение play объекту AVAudioPlayer), и, как показал мой простой эксперимент сравнения с внешним наблюдением, 60 ударов в минуту были слишком медленными - я получил эти временные интервалы (в секундах):

1.004915
1.009982
1.010014
1.010013
1.010028
1.010105
1.010095
1.010105

Мой вывод состоял в том, что после каждого интервала в 1 секунду проходит некоторое время служебной нагрузки и что через несколько десятков секунд дополнительное время (около 10 мсек) накапливается до заметной величины -- довольно плохо для метронома.Поэтому вместо измерения интервала между вызовами я решил измерить интервал total от первого вызова, чтобы ошибка не накапливалась.Другими словами, я заменил это условие:

while (continuePlaying && ((currentTime0 + [duration doubleValue]) >= currentTime1)

на это условие:

while (continuePlaying && ((_currentTime0 + _cnt * [duration doubleValue]) >= currentTime1 ))

, где теперь _currentTime0 и _cnt являются членами класса (извините, если это c ++jargon, я новичок в 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 с точностью не менее миллисекунды)).

Я буду рад услышать больше информации о том, что вызывает дополнительное время - 10 мсек звучит как вечность для современного процессора - хотя я не знаком со спецификациями процессора iPod (это iPod 4G)и Википедия говорит, что CUP - это PowerVR SGX GPU 535 при 200 МГц)

6 голосов
/ 27 апреля 2013

Если вам нужна точность <100 мсек, посмотрите <a href="http://developer.apple.com/library/ios/#documentation/QuartzCore/Reference/CADisplayLink_ClassRef/Reference/Reference.html"> CADisplayLink . Он вызывает селектор с регулярными интервалами, со скоростью 60 раз в секунду, то есть каждые 0,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 Гц, вам придется разделить displayLink.duration на 1,0 и округлить до ближайшего целого числа (не от пола).

3 голосов
/ 21 августа 2011

Я тоже очень заинтересован в этом, также заметил ошибку использования NSTimer и примера Metronome.

В настоящее время я изучаю протокол CAMediaTiming ... пока я не знаю, как его использовать, но это может оказаться более точным решением, поскольку оно используется для анимации.Я также могу быть совершенно неправ, так как игры замедляются, когда происходит слишком много.Я основываю свою теорию на моей любимой игре, где требуется точное время, когда «сражаются с противниками на улицах».Я думаю, что fps в игре был уменьшен до 30 по сравнению с консольными аналогами, которые работают на 60. Тем не менее, такт системы игры очень важен, например, комбо состоит из нажатия кнопки, затем еще одной ровно на 3 кадра позже, так что либо:

  • игра на iOS более щадящая по времени
  • разработчики внедрили свою собственную систему синхронизации
  • , иначе они используют цикл таймера Метронома или протокол CAMediaTiming.

Информация о времени анимации находится здесь: http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Animation_Types_Timing/Articles/Timing.html#//apple_ref/doc/uid/TP40006670-SW1

1 голос
/ 05 декабря 2012

Использование цикла while и режима сна для измерения метронома не является надежным способом решения этой проблемы, поскольку, как вы видели, он может привести к колебаниям и дрейфу синхронизации. Я считаю, что стандартным способом решения этой проблемы является использование Core Audio (так или иначе) для подачи непрерывного аудиопотока, содержащего тики метронома, разделенные правильной величиной молчания между ними, в зависимости от темпа. Поскольку вы знаете точную частоту дискретизации, вы можете очень точно рассчитать время тиков. К сожалению, самостоятельно создать аудио довольно сложно, чем то, что вы пытаетесь сделать, но этот вопрос Stackoverflow может помочь вам начать.

1 голос
/ 21 апреля 2012

Проблема более фундаментальная. IOS не является операционной системой реального времени, поэтому время, необходимое для выполнения различных операций, не является «определенным». Различается. Для точности вам понадобится ОС реального времени, например, новая операционная система RIM QNX.

0 голосов
/ 26 мая 2011

Рассматривали ли вы использовать NSTimer вместо этого "странного" решения while-loop?Мне кажется, что вы - не только немного - слишком усложняете очень, очень простые вещи ...

0 голосов
/ 31 декабря 2010

спасибо!Мне потребовалось немного времени, чтобы понять, как использовать CFAbsoluteTimeGetCurrent () для currentTime0 и currentTime1, и пришлось разыгрывать длительность (CGFloat) по отдельности (double myDuration = (double) duration), как при наличии кобылы, пытающейся использовать doubleValue.

Ваше предложение идеально подходит для моих нужд - вам удалось повысить точность?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...