CADisplayLink running lower frame rate on iOS5.1

后端 未结 3 459
南笙
南笙 2021-02-04 19:32

I\'m using CADisplayLink in my iPhone app.

Here is the relevant code:

SMPTELink = [CADisplayLink displayLinkWithTarget:self selector:@selec         


        
相关标签:
3条回答
  • 2021-02-04 20:05

    Basic Swift version of the other answers (minus the animation code)

    class BasicStopwatch {
    
        var timer: CADisplayLink!
        var firstTimestamp: CFTimeInterval!
        var elapsedTime: TimeInterval = 0
    
        let formatter: DateFormatter = {
            let df = DateFormatter()
            df.dateFormat = "mm:ss.SS"
            return df
        }()
    
        func begin() {
            timer = CADisplayLink(target: self, selector: #selector(tick))
            timer.preferredFramesPerSecond = 10 // adjust as needed
            timer.add(to: .main, forMode: .common)
        }
    
        @objc func tick() {
            if (self.firstTimestamp == nil) {
                print("Set first timestamp")
                self.firstTimestamp = timer!.timestamp
                return
            }
    
            elapsedTime = timer.timestamp - firstTimestamp
            /// Print raw elapsed time
            // print(elapsedTime)
    
            /// print elapsed time
            print(elapsedTimeAsString())
    
            /// If need to track frames
            // let totalFrames: Double = 20
            // let frameNumber = (elapsedTime * Double(timer!.preferredFramesPerSecond)).truncatingRemainder(dividingBy: totalFrames)
            // print("Frame ", frameNumber)
        }
    
        func elapsedTimeAsString() -> String {
            return formatter.string(from: Date(timeIntervalSinceReferenceDate: elapsedTime))
        }
    }
    

    Usage

    let watch = BasicStopwatch()
    watch.begin()
    
    0 讨论(0)
  • 2021-02-04 20:08

    Rob's answer is exactly right. You've no business worrying about the frame rate of CADisplayLink; in fact, you mustn't even expect that the timer will fire with anything like regularity. Your job is to divide up the desired animation in accordance with the desired time scale, and draw the frame you actually get each time the timer fires by adding up the accumulated timestamps.

    Here is sample code from my book:

    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 that code, the _frame value runs between 0 (we are just starting the animation) and 1 (we have finished the animation), and in the middle I just do whatever this particular situation requires to draw that frame. To make the animation take longer or shorter, just multiply a scale factor when setting the _frame ivar.

    Also note that you must never test in the Simulator, as the results are utterly meaningless. Only the device runs CADisplayLink properly.

    (Example comes from here: http://www.apeth.com/iOSBook/ch17.html#_cifilter_transitions)

    0 讨论(0)
  • 2021-02-04 20:13

    I can't speak to the iOS 5.x v 6.x differences, but when I use CADisplayLink, I never hard code stuff like "move x pixels/points" every iteration, but rather I look at the timestamp (or more accurately, the delta between my initial timestamp and the current timestamp) and calculate the location based upon how much time has elapsed, not by how many frames have passed. That way, frame rates don't affect the speed of the motion, but rather just the smoothness of it. (And the difference between 30 and 29 is likely to be indistinguishable.)

    To quote from the CADisplayLink Class Reference:

    Once the display link is associated with a run loop, the selector on the target is called when the screen’s contents need to be updated. The target can read the display link’s timestamp property to retrieve the time that the previous frame was displayed. For example, an application that displays movies might use the timestamp to calculate which video frame will be displayed next. An application that performs its own animations might use the timestamp to determine where and how displayed objects appear in the upcoming frame. The duration property provides the amount of time between frames. You can use this value in your application to calculate the frame rate of the display, the approximate time that the next frame will be displayed, and to adjust the drawing behavior so that the next frame is prepared in time to be displayed.


    As a random example, here I'm animating a UIBezierPath using the number of seconds that have elapsed as a parameter.

    Or, alternatively, if you're dealing with a sequence of UIImage frames, you could calculate the frame number as follows:

    @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
    }
    

    Or, better yet, to avoid risking losing a frame, go ahead and let this run at 60 fps and just determine if the frame needs updating and that way you'll reduce the risk of dropping a 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;
        }
    }
    

    But frequently, frame numbers aren't needed at all. For example, to move a UIView in a circle, you might do something like:

    - (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);
    }
    

    By the way, if you use Instruments to measure frame rate, it can seem slower than it really will be on a device. To matt's comment, for accurate frame rates, you should measure it programmatically on an actual device with a release build.

    0 讨论(0)
提交回复
热议问题