AV Foundation:AVCaptureVideoPreviewLayer和帧持续时间

6
我正在使用AV Foundation处理视频摄像头的帧(iPhone 4s,iOS 6.1.2)。我正在按照AV Foundation编程指南设置AVCaptureSession、AVCaptureDeviceInput和AVCaptureVideoDataOutput。一切都符合预期,我能够在 captureOutput:didOutputSampleBuffer:fromConnection: 委托中接收到帧。
我还设置了一个预览层,如下所示:
AVCaptureVideoPreviewLayer *videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
[videoPreviewLayer setFrame:self.view.bounds];
videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.view.layer insertSublayer:videoPreviewLayer atIndex:0];

事实上,在我的画面处理中我并不需要每秒30帧,而且我也无法以如此快的速度进行处理。因此我使用下面的代码来限制帧率:

// videoOutput is AVCaptureVideoDataOutput set earlier
AVCaptureConnection *conn = [videoOutput connectionWithMediaType:AVMediaTypeVideo];
[conn setVideoMinFrameDuration:CMTimeMake(1, 10)];
[conn setVideoMaxFrameDuration:CMTimeMake(1, 2)];

这样做可以很好地限制由captureOutput委托接收的帧。

然而,这也会限制预览图层每秒的帧数,导致预览视频非常不响应。

从文档中了解到,帧持续时间是在连接上独立设置的,预览图层确实有一个不同的AVCaptureConnection。检查[videoPreviewLayer connection]上的最小/最大帧持续时间表明,它确实设置为默认值(1/30和1/24),并且与AVCaptureVideoDataOutput的连接上设置的持续时间不同。

那么,是否可能仅在帧捕获输出上限制帧持续时间,并仍然看到预览视频的1/24-1/30帧持续时间?如何实现?

谢谢。

3个回答

5
虽然你说的没错,确实有两个AVCaptureConnection,但这并不意味着它们可以独立设置最小和最大帧持续时间。这是因为它们共享同一个物理硬件。
如果连接#1以每秒5帧的速率激活了滚动快门,并且帧持续时间为1/5秒,则没有办法让连接#2同时以每秒30帧的速率激活快门,并且帧持续时间为1/30秒。
要获得所需的效果需要两个摄像头!
接近你想要的效果的唯一方法是按照Kaelin Colclasure在3月22日回答中概述的方法进行。
然而,在该方法中,您还有更复杂的选项。例如,您可以使用计数器来决定要删除哪些帧,而不是使线程休眠。您可以使该计数器响应实际通过的帧速率(可以从传递给captureOutput:didOutputSampleBuffer:fromConnection:代理的元数据中获取图像数据,也可以通过手动计时帧来计算)。您甚至可以通过合成帧而不是删除它们来做出非常合理的长曝光效果 - 正如App Store中的许多“慢快门”应用程序所做的那样(撇开细节 - 例如不同的滚动快门伪影 - 一个以1/5秒扫描的帧和每个以1/25秒扫描然后粘在一起的五个帧之间实际上没有太大的区别)。
是的,这需要一些工作,但你正在尝试让一个视频摄像机实时地像两个摄像机一样运行,这永远不会容易。

"而那永远不会是容易的事。" 夸张说法 - aleclarson
嗨@Wildaker,你能帮忙解释一下帧持续时间以及使用不同值的影响吗?谢谢!http://stackoverflow.com/questions/34937008/exporting-videos-on-ios-understanding-and-setting-frame-duration-property - Crashalot
抱歉,@Crashalot,如果没有实验,我无法确定。这不是我做过的事情。 - Wildaker

3

可以这样理解: 您要求捕获设备限制帧时长,以便获得更好的曝光效果。 没问题。 您想要以更高的帧率预览。 如果您以更高的速率进行预览,则捕获设备(相机)将没有足够的时间来曝光帧,因此您会在捕获的帧上获得更好的曝光效果。 这就像要求在预览中看到不同的帧而非捕获的帧。

我认为,如果可能的话,这也会给用户带来负面体验。


你所说的很有道理。但是,如果你阅读“AV Foundation编程指南”和“AVCaptureConnection类参考”的文档,你会得到一个非常明确的印象,即预览层和实际输出设备具有不同的AVCaptureConnection。最小/最大帧持续时间参数在AVCaptureConnection对象上独立设置,并且确实对预览连接和捕获输出连接具有不同的值。虽然这并不令人惊讶,因为苹果的文档存在误导性和不一致的API,但你可以理解为什么这可能会完全使人迷失方向... - danielv
1
我还没有测试过,但是应该可以反过来实现:预览层低帧率,捕获的帧率更高。这可能就是为什么你会得到独立的捕获连接。 - gWiz
嗨@gWiz,你能帮忙解释一下帧持续时间以及使用不同值的影响吗?谢谢!http://stackoverflow.com/questions/34937008/exporting-videos-on-ios-understanding-and-setting-frame-duration-property - Crashalot

1
我有一个 Cocoa(Mac OS X)应用程序也遇到了同样的问题。这是我解决的方法:
首先,确保在单独的调度队列上处理捕获到的帧。同时确保丢弃任何尚未准备好处理的帧;虽然这是默认设置,但我仍然设置了下面的标志来记录我依赖它。
    videoQueue = dispatch_queue_create("com.ohmware.LabCam.videoQueue", DISPATCH_QUEUE_SERIAL);
    videoOutput = [[AVCaptureVideoDataOutput alloc] init];
    [videoOutput setAlwaysDiscardsLateVideoFrames:YES];
    [videoOutput setSampleBufferDelegate:self
                                   queue:videoQueue];
    [session addOutput:videoOutput];

然后,在处理委托中的帧时,您可以简单地让线程休眠所需的时间间隔。委托未唤醒以处理的帧将被静默丢弃。我实现了计算丢帧的可选方法,仅作为合理性检查;使用此技术时,我的应用程序从未记录任何丢帧。

- (void)captureOutput:(AVCaptureOutput *)captureOutput
  didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection;
{
    OSAtomicAdd64(1, &videoSampleBufferDropCount);
}

- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection;
{
    int64_t savedSampleBufferDropCount = videoSampleBufferDropCount;
    if (savedSampleBufferDropCount && OSAtomicCompareAndSwap64(savedSampleBufferDropCount, 0, &videoSampleBufferDropCount)) {
        NSLog(@"Dropped %lld video sample buffers!!!", savedSampleBufferDropCount);
    }
    // NSLog(@"%s", __func__);
    @autoreleasepool {
        CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
        CIImage * cameraImage = [CIImage imageWithCVImageBuffer:imageBuffer];
        CIImage * faceImage = [self faceImage:cameraImage];
        dispatch_sync(dispatch_get_main_queue(), ^ {
            [_imageView setCIImage:faceImage];
        });
    }
    [NSThread sleepForTimeInterval:0.5]; // Only want ~2 frames/sec.
}

希望这有所帮助。

谢谢,但我认为我不想用这种方式来处理,而是直接丢弃帧。当你限制帧持续时间时,AV框架会在低光条件下增加每个帧的曝光度,如果你只是丢弃帧,就无法获得这个好处。此外,每个帧的处理时间也会有所不同,当你限制最小/最大帧持续时间时,你可以让框架适应你的需求。我不想自己计算时间间隔。 - danielv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接