Apple有一个示例代码,名为Rosy Writer,显示如何捕捉视频并将效果应用于该视频。如何在AVFoundation预览视频时保持低延迟?
在代码的这一部分,在outputPreviewPixelBuffer
部分,苹果公司展示了它们如何通过删除陈旧的帧来保持预览延迟低。
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
if (connection == _videoConnection)
{
if (self.outputVideoFormatDescription == NULL) {
// Don't render the first sample buffer.
// This gives us one frame interval (33ms at 30fps) for setupVideoPipelineWithInputFormatDescription: to complete.
// Ideally this would be done asynchronously to ensure frames don't back up on slower devices.
[self setupVideoPipelineWithInputFormatDescription:formatDescription];
}
else {
[self renderVideoSampleBuffer:sampleBuffer];
}
}
else if (connection == _audioConnection)
{
self.outputAudioFormatDescription = formatDescription;
@synchronized(self) {
if (_recordingStatus == RosyWriterRecordingStatusRecording) {
[_recorder appendAudioSampleBuffer:sampleBuffer];
}
}
}
}
- (void)renderVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
CVPixelBufferRef renderedPixelBuffer = NULL;
CMTime timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
[self calculateFramerateAtTimestamp:timestamp];
// We must not use the GPU while running in the background.
// setRenderingEnabled: takes the same lock so the caller can guarantee no GPU usage once the setter returns.
@synchronized(_renderer)
{
if (_renderingEnabled) {
CVPixelBufferRef sourcePixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
renderedPixelBuffer = [_renderer copyRenderedPixelBuffer:sourcePixelBuffer];
}
else {
return;
}
}
if (renderedPixelBuffer)
{
@synchronized(self)
{
[self outputPreviewPixelBuffer:renderedPixelBuffer];
if (_recordingStatus == RosyWriterRecordingStatusRecording) {
[_recorder appendVideoPixelBuffer:renderedPixelBuffer withPresentationTime:timestamp];
}
}
CFRelease(renderedPixelBuffer);
}
else
{
[self videoPipelineDidRunOutOfBuffers];
}
}
// call under @synchronized(self)
- (void)outputPreviewPixelBuffer:(CVPixelBufferRef)previewPixelBuffer
{
// Keep preview latency low by dropping stale frames that have not been picked up by the delegate yet
// Note that access to currentPreviewPixelBuffer is protected by the @synchronized lock
self.currentPreviewPixelBuffer = previewPixelBuffer; // A
[self invokeDelegateCallbackAsync:^{ // B
CVPixelBufferRef currentPreviewPixelBuffer = NULL; // C
@synchronized(self) //D
{
currentPreviewPixelBuffer = self.currentPreviewPixelBuffer; // E
if (currentPreviewPixelBuffer) { // F
CFRetain(currentPreviewPixelBuffer); // G
self.currentPreviewPixelBuffer = NULL; // H
}
}
if (currentPreviewPixelBuffer) { // I
[_delegate capturePipeline:self previewPixelBufferReadyForDisplay:currentPreviewPixelBuffer]; // J
CFRelease(currentPreviewPixelBuffer); /K
}
}];
}
- (void)invokeDelegateCallbackAsync:(dispatch_block_t)callbackBlock
{
dispatch_async(_delegateCallbackQueue, ^{
@autoreleasepool {
callbackBlock();
}
});
}
经过几小时的试图了解此代码,我的大脑吸烟,我看不到这是如何完成的。
有人可以解释像我5岁,好吧,使它3岁,这个代码是如何做到这一点?
谢谢。
编辑:我用字母标记了outputPreviewPixelBuffer
这几行,以便轻松理解代码执行的顺序。
因此,该方法开始并且A
运行并且缓冲区被存储到属性self.currentPreviewPixelBuffer
中。 B
运行,并且本地变量currentPreviewPixelBuffer
被指定为NULL
。 D
运行并锁定self
。然后E
运行并将本地变量currentPreviewPixelBuffer
从NULL更改为值self.currentPreviewPixelBuffer
。
这是第一件没有道理的事情。为什么要创建一个变量currentPreviewPixelBuffer
将其分配给NULL
,并在下一行将其分配给self.currentPreviewPixelBuffer
?
下面这行更疯狂。为什么我询问currentPreviewPixelBuffer
是不是NULL
如果我只是将它分配给E
上的非NULL
值?然后H
被执行并且空值self.currentPreviewPixelBuffer
?
我不明白的一件事是:invokeDelegateCallbackAsync:
是异步的,对吗?如果它是异步的,则每次运行outputPreviewPixelBuffer
的方法是设置self.currentPreviewPixelBuffer = previewPixelBuffer
并调度一个块执行,可以自由运行。
如果outputPreviewPixelBuffer
被激发得更快,我们将有一堆堆积的执行块。
由于Kamil Kocemba
的解释,我不确定这些异步块是否正在测试,如果前一个完成执行并丢弃帧,如果不是。
另外,究竟是什么@syncronized(self)
锁定?它是否阻止self.currentPreviewPixelBuffer
被写入或读取?或者它是否锁定本地变量currentPreviewPixelBuffer
?如果@syncronized(self)
下的块与示波器同步,则I
的行将永远不会为NULL
,因为它正在E
上设置。
你能分享链接到源代码吗?我也有兴趣学习如何编辑来自摄像头的样本缓存器时的低延迟 – omarojo
链接位于第一段。 – SpaceDog