2013-02-19 14 views
6

Sto utilizzando CADisplayLink nella mia app per iPhone.CADisplayLink con frame rate inferiore su iOS5.1

Ecco il codice rilevante:

SMPTELink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onTick)]; 
SMPTELink.frameInterval = 2;//30fps 60/n = fps 
[SMPTELink addToRunLoop:[NSRunLoop mainRunLoop] 
        forMode:NSDefaultRunLoopMode]; 

onTick è quindi chiamato ogni fotogramma 30FPS (1/30 di secondo). Funziona GRANDE su iOS6 + - fa esattamente quello di cui ho bisogno. Tuttavia, quando ho eseguito la mia app su un iPhone 4s con iOS5.1, il metodo onTick è leggermente più lento rispetto alla controparte iOS6. Quasi come se lo stesse eseguendo 29FPS. Dopo un po ', era fuori sincronia con l'iOS6 iPhone 5.

Il codice nel metodo onTick non è in termini di tempo (che è stato uno dei miei pensieri ...), e non è l'iPhone perché l'applicazione funziona bene su un iPhone 4s con iOS6.

Does CADisplayLink funzionano in modo diverso in iOS5.1? Eventuali soluzioni alternative/soluzioni?

risposta

16

Non riesco a parlare con le differenze di iOS 5.xv 6.x, ma quando uso CADisplayLink, non ho mai codice rigido come "sposta x pixel/punti" ogni iterazione, ma piuttosto guardo lo timestamp (o più esattamente, il delta tra il mio timestamp iniziale e l'attuale timestamp) e calcolare la posizione in base al tempo trascorso, non dal numero di frame che sono passati. In questo modo, i frame rate non influenzano la velocità del movimento, ma piuttosto la sua fluidità. (E la differenza tra il 30 e il 29 è probabile che sia indistinguibile.)

Per citare dal CADisplayLink Class Reference:

Una volta che il collegamento del display è associata ad un ciclo di esecuzione, il selettore sul bersaglio viene chiamato quando i contenuti dello schermo devono essere aggiornati. L'obiettivo può leggere la proprietà timestamp del collegamento di visualizzazione per recuperare l'ora in cui è stato visualizzato il frame precedente. Ad esempio, un'applicazione che visualizza filmati potrebbe utilizzare il timestamp per calcolare quale fotogramma video verrà visualizzato successivamente. Un'applicazione che esegue le proprie animazioni potrebbe utilizzare il timestamp per determinare dove e come gli oggetti visualizzati vengono visualizzati nel frame successivo. La proprietà duration fornisce la quantità di tempo tra i frame. È possibile utilizzare questo valore nell'applicazione per calcolare la frequenza fotogrammi del display, il tempo approssimativo in cui verrà visualizzato il fotogramma successivo e per regolare il comportamento del disegno in modo che il fotogramma successivo sia preparato in tempo per essere visualizzato.


Come esempio a caso, here che sto animare un UIBezierPath utilizzando il numero di secondi trascorsi come parametro.

O, in alternativa, se hai a che fare con una sequenza di fotogrammi UIImage, si potrebbe calcolare il numero di telaio come segue:

@property (nonatomic) CFTimeInterval firstTimestamp; 

- (void)handleDisplayLink:(CADisplayLink *)displayLink 
{ 
    if (!self.firstTimestamp) 
     self.firstTimestamp = displayLink.timestamp; 

    CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp); 

    NSInteger frameNumber = (NSInteger)(elapsed * kFramesPerSecond) % kMaxNumberOfFrames; 

    // now do whatever you want with this frame number 
} 

O, meglio ancora, per evitare il rischio di perdere un frame, andare avanti e lascia che funzioni a 60 fps e determini solo se il frame deve essere aggiornato e in questo modo ridurrai il rischio di far cadere un frame.

- (void)handleDisplayLink:(CADisplayLink *)displayLink 
{ 
    if (!self.firstTimestamp) 
     self.firstTimestamp = displayLink.timestamp; 

    CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp); 

    NSInteger frameNumber = (NSInteger)(elapsed * kFramesPerSecond) % kMaxNumberOfFrames; 

    if (frameNumber != self.lastFrame) 
    { 
     // do whatever you want with this frame number 

     ... 

     // now update the "lastFrame" number property 

     self.lastFrame = frameNumber; 
    } 
} 

Ma spesso, numeri di frame non sono necessari affatto.Ad esempio, per spostare un UIView in un cerchio, si potrebbe fare qualcosa di simile:

- (void)handleDisplayLink:(CADisplayLink *)displayLink 
{ 
    if (!self.firstTimestamp) 
     self.firstTimestamp = displayLink.timestamp; 

    CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp); 

    self.animatedView.center = [self centerAtElapsed:elapsed]; 
} 

- (CGPoint)centerAtElapsed:(CFTimeInterval)elapsed 
{ 
    CGFloat radius = self.view.bounds.size.width/2.0; 

    return CGPointMake(radius + sin(elapsed) * radius, 
         radius + cos(elapsed) * radius); 
} 

A proposito, se si utilizzano strumenti per misurare il frame rate, può sembrare più lento di quanto in realtà sarà in un dispositivo. Al commento di Matt, per frequenze fotogrammi accurate, è necessario misurarlo a livello di codice su un dispositivo reale con una build di rilascio.

+0

Grande idea! Quindi, nel metodo onTick, ho il timestamp, poi il prossimo, ho il timestamp di nuovo - quindi confrontare per vedere se ottengo la differenza di 0.03333 secondi? Cosa succede se non lo faccio? Puoi approfondire il tuo concetto? – objectiveccoder001

+0

@ objectiveccoder001 Se c'è qualche ragione per cui hai assolutamente bisogno di 30 fps, allora sì, potresti fare qualcosa del genere. Vedere il mio esempio di codice che può determinare il numero di frame in base al tempo trascorso. Molte animazioni, tuttavia, non è necessario, ma è sufficiente calcolare la nuova posizione in base al tempo trascorso e non c'è alcun motivo per cui è vincolato a 30 fps (vs 29 vs 59 vs ecc.). – Rob

+0

FANTASTICO! Grazie mille. Cosa sono kMaxNumberOfFrames? – objectiveccoder001

3

La risposta di Rob è giusta. Non ti preoccupare del frame rate di CADisplayLink; in effetti, non devi neppure aspettarti che il timer funzioni con regolarità. Il tuo compito è quello di suddividere l'animazione desiderata in base alla scala temporale desiderata e tracciare il fotogramma che ottieni ogni volta che il timer scatta aggiungendo i timestamp accumulati.

Ecco il codice di esempio dal mio libro:

if (self->_timestamp < 0.01) { // pick up and store first timestamp 
    self->_timestamp = sender.timestamp; 
    self->_frame = 0.0; 
} else { // calculate frame 
    self->_frame = sender.timestamp - self->_timestamp; 
} 
sender.paused = YES; // defend against frame loss 

[_tran setValue:@(self->_frame) forKey:@"inputTime"]; 
CGImageRef moi3 = [self->_con createCGImage:_tran.outputImage 
            fromRect:_moiextent]; 
self->_iv.image = [UIImage imageWithCGImage:moi3]; 
CGImageRelease(moi3); 

if (self->_frame > 1.0) { 
    [sender invalidate]; 
    self->_frame = 0.0; 
    self->_timestamp = 0.0; 
} 
sender.paused = NO; 

In quel codice, il valore _frame corre tra 0 (stiamo appena iniziando l'animazione) e 1 (che abbiamo finito l'animazione), e nel nel mezzo faccio solo ciò che questa particolare situazione richiede per disegnare quella cornice. Per rendere l'animazione più lunga o più breve, basta moltiplicare un fattore di scala quando si imposta il ivar _frame.

Si noti inoltre che non è necessario eseguire mai il test nel simulatore, in quanto i risultati sono assolutamente privi di significato. Solo il dispositivo esegue correttamente CADisplayLink.

(esempio viene da qui: http://www.apeth.com/iOSBook/ch17.html#_cifilter_transitions)

Problemi correlati