Подтвердить что ты не робот

Точный выбор времени в iOS

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

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

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

4b9b3361

Ответ 1

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

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

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 являются членами класса (извините, если это С++-жаргон, я совершенно новичок в 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 CPU (это iPod 4G и Wikipedia говорит, что CUP - PowerVR SGX GPU 535 @200 МГц)

Ответ 2

Если вам нужна точность < 100msec, просмотрите 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 Гц, вам нужно разделить displayLink.duration на 1.0 и округлить до ближайшего целого (а не пола).

Ответ 3

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

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

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

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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