0

我正在尝试将一些 ObjC 代码移植到 Swift。该代码是 Flutter Camera-Plugin(第 265 行)的一部分。该插件允许实时预览相机。

作为一名 Swift 程序员,我不知道这里发生了什么。做什么OSAtomicCompareAndSwapPtrBarrier()以及如何将这段代码转移到 Swift?

- (CVPixelBufferRef)copyPixelBuffer {
  CVPixelBufferRef pixelBuffer = _latestPixelBuffer;
  while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, (void **)&_latestPixelBuffer)) {
    pixelBuffer = _latestPixelBuffer;
  }
  return pixelBuffer;
}
4

2 回答 2

3

当有多个线程并且需要确保在分配和检查变量之间,事情没有改变时,使用 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。

于 2019-02-24T11:11:09.617 回答
1

在 captureOutput 委托方法中

let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!

接着

public func copyPixelBuffer() -> Unmanaged<CVPixelBuffer>? {
    if(pixelBuffer == nil){
        return nil
    }
    return  Unmanaged<CVPixelBuffer>.passRetained(pixelBuffer)
}
于 2019-12-17T21:12:14.490 回答