NSTimer提供不精确的结果

时间:2015-06-25 17:33:16

标签: ios avfoundation nstimer nstimeinterval

我有一个相机应用程序,我试图将捕获长度限制为完全 15秒。

我尝试了两种不同的方法,但它们都没有令我满意。

第一种方法是每秒发射一次重复计时器:

self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(countTime:) userInfo:[NSDate date] repeats:YES];

- (void)countTime:(NSTimer*)sender {
    NSDate *start = sender.userInfo;
    NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:start];
    NSInteger time = round(duration);
    if (time > 15) { 
        [self capture:nil]; // this stops capture
    }
}

这给了我一个15秒的视频8/10次,周期性的16秒...我在这里尝试了NSTimeInterval double和舍入整数的混合,没有明显的区别......

第二种方法是在所需的持续时间之后触发一次选择器,如下所示:

self.timer = [NSTimer scheduledTimerWithTimeInterval:15.0f target:self selector:@selector(capture:) userInfo:nil repeats:NO];

这只是调用捕获方法 - 直接停止摄像头捕获 - 并给我相同的结果......

我在这里有什么东西可以忽略吗?

现在,因为我已经测试了一些调整浮点值作为上限( 14.5,15.0,15.1,15.5,16.0等),我几乎总是看到一个16秒后的视频几次尝试,我开始怀疑它是否只是AVFoundation花了一秒钟来冲洗缓冲区... ???

2 个答案:

答案 0 :(得分:2)

NSTimer无法保证在您希望它发射时触发:

来自Apple的文档:

  

计时器不是实时机制;只有当添加了计时器的其中一个运行循环模式正在运行并且能够检查计时器的触发时间是否已经过去时,它才会触发。由于典型的运行循环管理各种输入源,因此定时器的时间间隔的有效分辨率被限制在50-100毫秒的量级。如果在长时间标注期间或在运行循环处于不监视计时器的模式下发生计时器的触发时间,则计时器在下次运行循环检查计时器之前不会触发。因此,计时器可能发射的实际时间可能是在计划的发射时间之后的重要时间段。另见Timer Tolerance。

但是为了回答你的问题,我曾经为一家拥有最多15秒视频的公司工作。我没有写视频代码,但我认为我们事后使用了AVComposition来确保视频不超过15秒。即便如此,它有时可能会缩短一帧。见How do I use AVFoundation to crop a video

答案 1 :(得分:0)

感谢Paul和Linuxious的评论和回答......以及Rory开箱即用的思考(有趣的选项)。

是的,最后很明显NSTimer本身并不足够。

最后,我会调用captureOutput委托方法来触发,测试资产的长度,并适当修剪构图。

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
      fromConnections:(NSArray *)connections error:(NSError *)error
{
    _isRecording = NO;

    AVURLAsset *videoAsset = [AVURLAsset assetWithURL:outputFileURL];
    CMTime length = [videoAsset duration];
    CMTimeShow(length);

    if(CMTimeGetSeconds(length) > 15)
    {
        NSLog(@"Capture Longer Than 15 Seconds - Attempting to Trim");

        Float64 preferredDuration = 15;
        int32_t preferredTimeScale = 30;
        CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeMakeWithSeconds(preferredDuration, preferredTimeScale));

        AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:videoAsset presetName:AVAssetExportPresetHighestQuality];
        exportSession.outputURL = outputFileURL;
        exportSession.outputFileType = AVFileTypeQuickTimeMovie;
        exportSession.timeRange = timeRange;

        NSError *err = nil;
        [[NSFileManager defaultManager] removeItemAtURL:outputFileURL error:&err];
        if (err) {
            NSLog(@"Error deleting File: %@", [err localizedDescription]);
        }
        else {
            [exportSession exportAsynchronouslyWithCompletionHandler:^{
                if (exportSession.status == AVAssetExportSessionStatusCompleted) {
                    NSLog(@"Export Completed - Passing URL to Delegate");
                    if ([self.delegate respondsToSelector:@selector(didFinishRecordingToOutputFileAtURL:error:)]) {
                        [self.delegate didFinishRecordingToOutputFileAtURL:outputFileURL error:error];
                    }
                }
                else if(exportSession.status == AVAssetExportSessionStatusFailed) {
                    NSLog(@"Export Error: %@", [exportSession.error localizedDescription]);
                    if ([self.delegate respondsToSelector:@selector(didFinishRecordingToOutputFileAtURL:error:)]) {
                        [self.delegate didFinishRecordingToOutputFileAtURL:outputFileURL error:exportSession.error ];
                    }
                }
            }];
        }

    }

}