当有多个线程并且需要确保在分配和检查变量之间,事情没有改变时,使用 OSAtomic。让我详细解释一下这里为什么需要它,但总而言之,在 Swift 中您不必担心这一点,因为 Swift 运行时会处理这种特殊情况。
详细解释:有两个活动线程(DispatchQueues),一个捕获CVPixelBuffer captureOutput:
,另一个调用copyPixelBuffer:
传递CVPixelBuffer进行flutter。
调用时captureOutput:
,使用 提取当前帧CMSampleBufferGetImageBuffer
,这需要调用者调用 CFRetain 将缓冲区保留在内存中,当不再需要缓冲区时,将调用 CFRelease 以释放内存。幸运的是,Flutter 会自动在传递的 pixelBuffer 上调用 CFRelease,所以我们只需要调用 CFRetain 就不必担心释放缓冲区,因为 Flutter 会为我们做这件事。但是,如果在旧帧移交给 Flutter 之前获得了新帧,我们将不得不在旧帧上调用 CFRelease,因为它永远不会被交给 Flutter。
那么我们如何知道缓冲区是否已经交给 Flutter 呢?好吧,每当缓冲区被移交给颤振时,我们都可以设置_latestPixelBuffer = nil;
并且每当产生新的缓冲区时,我们可以检查if _latestPixelBuffer != nil
我们是否可以在其上调用 CFRelease。因此,如果没有 OSAtomic,该函数将(错误地)看起来像:
- (void)captureOutput:(AVCaptureOutput *)output
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection {
CVPixelBufferRef newBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CFRetain(newBuffer);
// WRONG IMPLEMENTATION FOLLOWS
CVPixelBufferRef old = _latestPixelBuffer;
// Reference Point 1
_latestPixelBuffer = newBuffer;
if (old != nil) {
CFRelease(old);
}
- (CVPixelBufferRef)copyPixelBuffer {
CVPixelBufferRef pixelBuffer = _latestPixelBuffer;
// WRONG IMPLEMENTATION
_latestPixelBuffer = nil;
return pixelBuffer;
}
由于它是多线程的(实际上 Flutter 使用 GPU 线程),可能会出现这样一种情况,当我们到达// Reference Point 1
时,我们已经将旧帧的引用复制到其中old
并即将保存新帧_latestPixelBuffer
但在我们可以执行之前这一行,flutter的线程调用copyPixelBuffer。所以我们将旧框架交给 Flutter,现在 Flutter 负责在其上调用 CFRelease。但是,我们的线程仍然会看到 _old 仍然引用旧帧(因为该行是在 Flutter 调用之前执行的),因此我们会不小心在其上调用 CFRelease。
所以我们需要一个保证,captureOutput:
当我们赋值时_latestPixelBuffer = newBuffer
,flutter 的线程无法获取旧缓冲区,或者如果确实如此,那么我们希望我们old
也引用 nil,这样我们就不会在其上调用 CFRelease。这是OSAtomicCompareAndSwapPtrBarrier
派上用场的地方。
- (void)captureOutput:(AVCaptureOutput *)output
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection {
CVPixelBufferRef newBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CFRetain(newBuffer);
CVPixelBufferRef old = _latestPixelBuffer;
while (!OSAtomicCompareAndSwapPtrBarrier(old, newBuffer, (void **)&_latestPixelBuffer)) {
old = _latestPixelBuffer;
}
if (old != nil) {
CFRelease(old);
}
它会检查是否_latestPixelBuffer
== old
,然后才会分配_latestPixelBuffer = newBuffer
。如果在此期间调用了颤振的线程,_latestPixelBuffer
则会将其设置为nil
内部copyPixelBuffer:
,并且 OSAtomic 将从此失败_latestPixelBuffer != old
。这是 while 循环体发挥作用的地方,在这里我们old = _latestPixelBuffer;
再次设置,但这一次old
将获得由颤振线程设置的新值。现在,当调用 OSAtomic 时,它会设置_latestPixelBuffer = newBuffer
.
为此,所有更改值的线程_latestPixelBuffer
都 需要使用OSAtomicCompareAndSwapPtrBarrier
. 这就是copyPixelBuffer:
也使用此功能的原因:
- (CVPixelBufferRef)copyPixelBuffer {
CVPixelBufferRef pixelBuffer = _latestPixelBuffer;
while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, (void **)&_latestPixelBuffer)) {
pixelBuffer = _latestPixelBuffer;
}
return pixelBuffer;
}
在 Swift 中,我们不会CMSampleBufferGetImageBuffer
在 Swift 运行时为我们的返回值上调用 CFRetain。由于运行时为我们调用 CFRetain,因此运行时也有责任在其上调用 CFRelease。但是,在 中copyPixelBuffer
,我们需要返回Unmanaged<CVPixelBuffer>.passRetained(latestPixelBuffer!)
以便 Flutter 可以在其上调用 CFRelease。