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