【问题标题】:How do I update a CALayer with a CVPixelBuffer/IOSurface?如何使用 CVPixelBuffer/IOSurface 更新 CALayer?
【发布时间】:2021-02-28 00:43:21
【问题描述】:

我有一个支持 IOSurface 的 CVPixelBuffer,它正在以 30fps 的速度从外部源更新。我想在 NSView 中呈现图像数据的预览——对我来说最好的方法是什么?

我可以直接在视图上设置 CALayer 的 .contents,但这只会在我的视图第一次更新时更新(或者说,如果我调整了视图的大小)。我一直在仔细研究文档,但无法确定在层或视图上正确调用 needsDisplay 以让视图基础结构知道刷新自身,尤其是当更新来自视图外部时。

理想情况下,我只需将 IOSurface 绑定到我的层,我对其所做的任何更改都会传播,但我不确定这是否可能。

class VideoPreviewController: NSViewController, VideoFeedConsumer {
    let customLayer : CALayer = CALayer()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do view setup here.
        print("Loaded our video preview")
        
        view.layer?.addSublayer(customLayer)
        customLayer.frame = view.frame
        
        // register our view with the browser service
        VideoFeedBrowser.instance.registerConsumer(self)
    }
    
    override func viewWillDisappear() {
        // deregister our view from the video feed
        VideoFeedBrowser.instance.deregisterConsumer(self)

        super.viewWillDisappear()
    }
    
    // This callback gets called at 30fps whenever the pixelbuffer is updated
    @objc func updateFrame(pixelBuffer: CVPixelBuffer) {

        guard let surface = CVPixelBufferGetIOSurface(pixelBuffer)?.takeUnretainedValue() else {
            print("pixelbuffer isn't IOsurface backed! noooooo!")
            return;
        }

        // Try and tell the view to redraw itself with new contents?
        // These methods don't work
        //self.view.setNeedsDisplay(self.view.visibleRect)
        //self.customLayer.setNeedsDisplay()
        self.customLayer.contents = surface

    }
    
}

这是我对基于 NSView 而不是基于 NSViewController 的缩放版本的尝试,它也不能正确更新(或就此而言正确缩放):

class VideoPreviewThumbnail: NSView, VideoFeedConsumer {
   

    required init?(coder decoder: NSCoder) {
        super.init(coder: decoder)
        self.wantsLayer = true
        
        // register our view with the browser service
        VideoFeedBrowser.instance.registerConsumer(self)
    }
    
    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        self.wantsLayer = true
        
        // register our view with the browser service
        VideoFeedBrowser.instance.registerConsumer(self)
    }
    
    deinit{
        VideoFeedBrowser.instance.deregisterConsumer(self)
    }
    
    override func updateLayer() {
        // Do I need to put something here?
        print("update layer")
    }
    
    @objc
    func updateFrame(pixelBuffer: CVPixelBuffer) {
        guard let surface = CVPixelBufferGetIOSurface(pixelBuffer)?.takeUnretainedValue() else {
            print("pixelbuffer isn't IOsurface backed! noooooo!")
            return;
        }
        self.layer?.contents = surface
        self.layer?.transform = CATransform3DMakeScale(
            self.frame.width / CGFloat(CVPixelBufferGetWidth(pixelBuffer)),
            self.frame.height / CGFloat(CVPixelBufferGetHeight(pixelBuffer)),
            CGFloat(1))
    }

}

我错过了什么?

【问题讨论】:

    标签: swift calayer nsview cvpixelbuffer


    【解决方案1】:

    也许我错了,但我认为你是在后台线程上更新你的 NSView。 (我想updateFrame 的回调是在后台线程上)

    如果我是对的,当您想更新 NSView 时,请将您的 pixelBuffer 转换为您想要的任何内容(NSImage?),然后在主线程上调度它。

    伪代码(我不经常使用 CVPixelBuffer,所以我不确定这是转换为 NSImage 的正确方法)

    let ciImage = CIImage(cvImageBuffer: pixelBuffer)
    let context = CIContext(options: nil)
    
    let width = CVPixelBufferGetWidth(pixelBuffer)
    let height = CVPixelBufferGetHeight(pixelBuffer)
    
    let cgImage = context.createCGImage(ciImage, from: CGRect(x: 0, y: 0, width: width, height: height))
    
    let nsImage = NSImage(cgImage: cgImage, size: CGSize(width: width, height: height))
    
    DispatchQueue.main.async {
        // assign the NSImage to your NSView here
    }
    

    另一个问题:我做了一些测试,您似乎无法将 IOSurface 直接分配给 CALayer 的内容。

    我试过这个:

        let textureImageWidth = 1024
        let textureImageHeight = 1024
    
        let macPixelFormatString = "ARGB"
        var macPixelFormat: UInt32 = 0
        for c in macPixelFormatString.utf8.reversed() {
           macPixelFormat *= 256
           macPixelFormat += UInt32(c)
        }
    
        let ioSurface = IOSurfaceCreate([kIOSurfaceWidth: textureImageWidth,
                        kIOSurfaceHeight: textureImageHeight,
                        kIOSurfaceBytesPerElement: 4,
                        kIOSurfaceBytesPerRow: textureImageWidth * 4,
                        kIOSurfaceAllocSize: textureImageWidth * textureImageHeight * 4,
                        kIOSurfacePixelFormat: macPixelFormat] as CFDictionary)!
    
        IOSurfaceLock(ioSurface, IOSurfaceLockOptions.readOnly, nil)
        let test = CIImage(ioSurface: ioSurface)
        IOSurfaceUnlock(ioSurface, IOSurfaceLockOptions.readOnly, nil)
        
        v1?.layer?.contents = ioSurface
    

    v1 是我的观点。没有效果

    即使使用 CIImage 也没有效果(仅最后几行)

        IOSurfaceLock(ioSurface, IOSurfaceLockOptions.readOnly, nil)
        let test = CIImage(ioSurface: ioSurface)
        IOSurfaceUnlock(ioSurface, IOSurfaceLockOptions.readOnly, nil)
        
        v1?.layer?.contents = test
    

    如果我创建一个 CGImage 就可以了

        IOSurfaceLock(ioSurface, IOSurfaceLockOptions.readOnly, nil)
        let test = CIImage(ioSurface: ioSurface)
        IOSurfaceUnlock(ioSurface, IOSurfaceLockOptions.readOnly, nil)
        
        let context = CIContext.init()
        let img = context.createCGImage(test, from: test.extent)
        v1?.layer?.contents = img
    

    【讨论】:

    • 这是个好主意。我已经尝试从主线程运行所有这些 - 没有区别。另一点是你可以直接将一个IOSurface分配给一个CALayer的.contents,(有效),只是不会发生更新。
    • 您可以尝试在.contents = ... 之后添加CATransaction.flush() 吗?始终在主线程上。我正在查看我的一些旧项目(目标 C),我发现了它......也许它有帮助
    • @andrew 我做了一些测试并为答案添加了更多细节。我认为你不能直接将 IOSurface 分配给 CALayer 内容,在我的情况下,它甚至第一次都不起作用,但我总是要通过一个 CGImage。使用示例代码查看我的答案中的“另一个捕获”部分。
    • 您是否尝试使用兼容 CoreAnimation 的 IOSurface 定义一个 PixelBuffer?这对我有用。试试var bufferAttributes = [kCVPixelBufferIOSurfaceCoreAnimationCompatibilityKey:true] as CFDictionary; let err = CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_32BGRA, bufferAttributes, &pb)russbishop.net/cross-process-rendering
    【解决方案2】:

    如果您有一些 IBAction 对其进行更新,则使用 didSet 块创建一个观察变量,并且每当触发 IBAction 时,更改其值。还记得在该块中编写更新时要运行的代码。

    我建议将变量设为Int,将其默认值设置为0,并在每次更新时添加1

    您可以将NSView 转换为NSImageView,以便在NSView 上显示IMAGE 数据的部分进行操作。

    【讨论】:

      【解决方案3】:

      你需要将像素缓冲区转换为CGImage并转换为图层,这样你就可以改变主视图的图层。 请尝试此代码

      @objc
      func updateFrame(pixelBuffer: CVPixelBuffer) {
          guard let surface = CVPixelBufferGetIOSurface(pixelBuffer)?.takeUnretainedValue() else {
              print("pixelbuffer isn't IOsurface backed! noooooo!")
              return;
          }
          void *baseAddr = CVPixelBufferGetBaseAddress(pixelBuffer);
          size_t width = CVPixelBufferGetWidth(pixelBuffer);
          size_t height = CVPixelBufferGetHeight(pixelBuffer);
          CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
          CGContextRef cgContext = CGBitmapContextCreate(baseAddr, width, height, 8, CVPixelBufferGetBytesPerRow(pixelBuffer), colorSpace, kCGImageAlphaNoneSkipLast);
          CGImageRef cgImage = CGBitmapContextCreateImage(cgContext);
          CGContextRelease(cgContext);
          
          let outputImage = UIImage(cgImage: outputCGImage, scale: 1, orientation: img.imageOrientation)
          let newLayer:CGLayer = CGLayer.init(cgImage: outputImage)
          self.layer = newLayer
          
          CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
          CVPixelBufferRelease(pixelBuffer);
      }
      

      【讨论】:

      • 这类似于我使用VTCreateCGImageFromPixelBuffer 工作的解决方案,但它对CPU 非常密集。您可以直接将IOSurface 分配给CALayer.contents 并进行渲染——这只是我很难处理的标记脏东西。
      【解决方案4】:

      我自己遇到了这个问题,解决方案是双缓冲 IOSurface 源:使用两个 IOSurface 对象而不是一个并渲染到当前表面,将表面设置为图层内容,然后在下一个渲染过程中使用备用(后/前)表面,然后交换。

      似乎将 CALayer.contents 两次设置为相同的 CVPixelBufferRef 无效。但是,如果您在两个 IOSurfaceRef 之间交替使用,效果会非常好。

      也可以通过将图层内容设置为 nil 然后重置来使图层内容无效。我没有尝试这种情况,但正在使用双缓冲技术。

      【讨论】:

        猜你喜欢
        • 2015-10-27
        • 2019-03-19
        • 2016-05-29
        • 1970-01-01
        • 2012-05-27
        • 2019-08-12
        • 1970-01-01
        • 1970-01-01
        • 2013-05-30
        相关资源
        最近更新 更多