Sincronización precisa en iOS

Estoy mirando el código de ejemplo 'Metrónomo' del iOS SDK ( http://developer.apple.com/library/ios/#samplecode/Metronome/Introduction/Intro.html ). Estoy ejecutando el metrónomo a 60 BPM, lo que significa una marca cada segundo. Cuando miro un reloj externo (el reloj de la PC), veo que el metrónomo se está ejecutando demasiado lento, se pierde alnetworkingedor de un latido cada minuto, que es la aplicación. 15mseg de error constante. La pieza de código relevante es:

- (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] != NSOrdenetworkingDescending)) { if ([soundPlayerThread isCancelled] == YES) { continuePlaying = NO; } [NSThread sleepForTimeInterval:0.01]; [currentTime release]; currentTime = [[NSDate alloc] init]; } [curtainTime release]; [currentTime release]; [loopPool drain]; } [pool drain]; } 

Dónde

auto.duración

es de 1.0 segundo en el caso de 60 BPM. Me pregunto de dónde proviene este error, y cómo puedo hacer un contador de intervalos / timer más preciso.

EDIT: El problema también existe cuando cambio el time de suspensión a valores más pequeños, por ejemplo, .001.

EDIT2 (actualización): El problema también existe cuando utilizo el método CFAbsoluteTimeGetCurrent() para la synchronization. Cuando uso el mismo método para medir el time entre un toque de events, el time parece correcto: toco una vez por segundo (mientras veo un reloj), y la velocidad medida es de 60 BPM (en promedio). Entonces supongo que debe ser un problema con el NSThread (?). Otra cosa es que en el dispositivo (iPod) el problema parece más severo que en el simulador.

Ok, tengo algunas respuestas después de hacer algunas testings más, así que lo estoy compartiendo con cualquier persona que esté interesada.

He colocado una variable para medir los intervalos de time entre ticks, dentro del método de play (el método que realmente envía el post de play al object AVAudioPlayer ), y como mi sencillo experimento de comparación con el reloj externo mostró, los 60 BPM fueron demasiado lento: obtuve estos intervalos de time (en segundos):

 1.004915 1.009982 1.010014 1.010013 1.010028 1.010105 1.010095 1.010105 

Mi conclusión fue que el time de sobrecarga transcurre después de que se count cada intervalo de 1 segundo, y ese time extra (aproximadamente 10 ms) se acumula a una cantidad notable después de algunas decenas de segundos, bastante malo para un metrónomo. Entonces, en lugar de medir el intervalo entre llamadas, decidí medir el intervalo total desde la primera llamada, para que el error no se acumule. En otras palabras, he reemplazado esta condición:

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

con esta condición:

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

donde ahora _currentTime0 y _cnt son miembros de la class (lo siento si es una jerga de c ++, soy bastante nuevo en Obj-C), el primero contiene la timestamp de la primera llamada al método y este último es un número de ticks (== llamadas de function). Esto dio como resultado los siguientes intervalos de time medidos:

 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 

y es evidente incluso sin calcular el promedio, que estos valores fluctúan alnetworkingedor de 1.0 segundo (y el promedio es cercano a 1.0 con al less un milisegundo de precisión).

Estaré encantado de escuchar más ideas sobre lo que hace que transcurra el time extra -10 mseg suena como la eternidad para una CPU moderna- aunque no estoy familiarizado con las especificaciones de la CPU del iPod (es iPod 4G y Wikipedia dice que CUP es PowerVR SGX GPU 535 @ 200 MHz)

Si necesita una precisión <100msec, mire en CADisplayLink . Llama a un selector a intervalos regulares, tan rápido como 60 veces por segundo, es decir, cada .0166667 seg.

Si quieres hacer tu testing de metrónomo, establecerás frameInterval en 60 para que se vuelva a llamar una vez por segundo.

 self.syncTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(syncFinetworking:)]; self.syncTimer.frameInterval = 60; [self.syncTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; -(void)syncFinetworking:(CADisplayLink *)displayLink { static NSTimeInterval lastSync = 0; NSTimeInterval now = CACurrentMediaTime(); if (lastSync > 0) { NSLog(@"interval: %f", now - lastSync); } lastSync = now; } 

En mis testings, ese timer se invoca constantemente cada segundo dentro de 0.0001 seg.

En caso de que se esté ejecutando en un dispositivo con una frecuencia de actualización diferente a 60Hz, tendría que dividir displayLink.duration en 1.0 y networkingondear al integer más cercano (no el piso).

Estoy muy interesado en esto también, también noté la falacia de usar NSTimer y el ejemplo del Metrónomo.

Actualmente estoy buscando el protocolo CAMediaTiming … pero no tengo idea de cómo usarlo, pero podría ser una solución más precisa, ya que se utiliza para animaciones. También podría estar completamente equivocado ya que los juegos se ralentizan cuando sucede demasiado. Estoy basando mi teoría en mi juego favorito donde se requiere un cronometraje preciso cuando "luchan contra los oponentes en las calles". Los fps del juego se han networkingucido a 30, creo que en comparación con sus contrapartes de la console que relojes a 60. Sin embargo, el time del sistema de combo del juego es muy importante, por ejemplo, un combo consiste en pulsar un button y luego otro, exactamente 3 fotogtwigs más tarde,

  • el juego en iOS es más indulgente con el time
  • los desarrolladores implementaron su propio sistema de cronometraje
  • de lo contrario, están utilizando el ciclo del timer del metrónomo o el protocolo CAMediaTiming.

Puede encontrar información sobre el time en las animaciones aquí: http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Animation_Types_Timing/Articles/Timing.html#//apple_ref/doc/uid/TP40006670-SW1

El problema es más fundamental. IOS no es un sistema operativo en time real, por lo que el time que lleva realizar varias operaciones no es "determinado". Varía. Para mayor precisión, necesitaría un sistema operativo en time real como el sistema operativo New QNX de RIM.

Usando un bucle while y sleep to time un metrónomo no es una forma robusta de resolver este problema, ya que es probable que produzca un time que es nervioso y que se desvía, como ya ha visto. Creo que la forma estándar de resolver este problema es utilizar Core Audio (de una manera u otra) para alimentar una secuencia continua de audio que contenga las garrapatas del metrónomo separadas por la cantidad correcta de silencio entre ellas, según el tempo. Debido a que conoce la frecuencia de muestreo exacta, puede cronometrar sus ticks con mucha precisión. Desafortunadamente, generar el audio usted mismo es bastante más difícil que lo que intenta hacer, pero esta pregunta de Stackoverflow podría ayudarle a comenzar.

Gracias ! me llevó un time descubrir que usaba CFAbsoluteTimeGetCurrent () para currentTime0 y currentTime1, y tuvo que ejecutar la duración (CGFloat) por separado (double myDuration = (doble) duration) que tenía una yegua intentando usar doubleValue.

Su sugerencia funciona perfectamente para mis necesidades: ¿ha logrado boost la precisión?

¿Consideró usar un NSTimer en lugar de esta "extraña" while-loop-solution? Me parece que eres, no solo un poco, muy complicado aquí, muy simples …