繁体   English   中英

使用 Combine 在 SwiftUI 视图上显示实时摄像头画面的效率如何?

[英]How efficient is using Combine to show live camera feed on SwiftUI view?

本 Kodeco 教程展示了如何使用 Combine 在 SwiftUI 中显示 iOS 实时摄像头源。

以下是执行此操作的基本部分(在删除非必要的代码行之后):

class FrameManager : NSObject, ObservableObject
{
    @Published var current: CVPixelBuffer?

    let videoOutputQueue = DispatchQueue(label: "com.raywenderlich.VideoOutputQ",
                                         qos: .userInitiated,
                                         attributes: [],
                                         autoreleaseFrequency: .workItem)
}


extension FrameManager: AVCaptureVideoDataOutputSampleBufferDelegate
{
    func captureOutput(_ output: AVCaptureOutput,
                       didOutput sampleBuffer: CMSampleBuffer,
                       from connection: AVCaptureConnection)
    {
        if let buffer = sampleBuffer.imageBuffer
        {
            DispatchQueue.main.async
            {
                self.current = buffer
            }
        }
    }
}


extension CGImage
{
    static func create(from cvPixelBuffer: CVPixelBuffer?) -> CGImage?
    {
        guard let pixelBuffer = cvPixelBuffer else
        {
            return nil
        }

        var image: CGImage?
        VTCreateCGImageFromCVPixelBuffer(pixelBuffer, 
                                         options: nil,
                                         imageOut: &image)

        return image
    }
}


class ContentViewModel : ObservableObject
{
    @Published var frame: CGImage?

    private let context = CIContext()

    private let frameManager = FrameManager.shared

    init()
    {
        setupSubscriptions()
    }

    func setupSubscriptions()
    {
        frameManager.$current
            .receive(on: RunLoop.main)
            .compactMap
        { buffer in
            guard let image = CGImage.create(from: buffer) else
            {
                return nil
            }

            var ciImage = CIImage(cgImage: image)

            return self.context.createCGImage(ciImage, from: ciImage.extent)
        }
        .assign(to: &$frame)
    }
}


struct ContentView : View
{
    @StateObject private var model = ContentViewModel()

    var body: some View
    {
        ZStack {
            FrameView(image: model.frame)
                .edgesIgnoringSafeArea(.all)
        }
    }
}


struct FrameView : View
{
    var image: CGImage?

    var body: some View
    {
        if let image = image
        {
            GeometryReader
            { geometry in
                Image(image, scale: 1.0, orientation: .up, label: Text("Video feed"))
                    .resizable()
                    .scaledToFill()
                    .frame(width: geometry.size.width,
                           height: geometry.size.height,
                           alignment: .center)
                    .clipped()
            }
        }
    }
}

虽然它正在工作,但是否将每个CVPixelBuffer转换为 SwiftUI Image并使用 Combine/bindings 在屏幕上显示这些图像是显示实时相机提要的一种好/有效的方式?

而且,如果图像处理速度太慢而无法跟上AVCaptureVideoDataOutputSampleBufferDelegate提要,会发生什么情况; 会跳过过时的帧吗? (完整代码有一些 CI 过滤器,会大大降低速度。)

长话短说,它会根据您的用例高效。

如果您想在没有任何过滤器的情况下预览相机并将您的session.sessionPreset设置为hd1280x720或更低; 它会为你工作。 请注意,如果您的应用没有为其他服务消耗 memory。

问题:

这种方法唯一的问题是 memory。当您捕获 4K 时,图像尺寸巨大,您将通过并呈现在视野中。 由于 SwiftUI 和@Published属性包装器是Combine框架的一部分,它会在每次出现新图像时更新视图。

现在想象一下3840x2160需要多少空间才能达到 3840x2160 。 如果您使用CIImage和 applyFilter 为每个帧添加一个额外的滤镜层,它会导致预览出现一些滞后并消耗大量 memory。此外,为了应用滤镜,我们必须使用CIContext() ,创建起来很昂贵。

但是通过使用AVCaptureVideoPreviewLayer即使我们想预览4k也不会有这些问题。

这些测试是在AVAssetWriter编写视频/音频时捕获的

SwiftUI Combine Video Preview 图层效率

建议:

我的建议是创建一个基于UIViewRepresentable的视图,并在其中使用AVCaptureVideoPreviewLayer作为视图的预览层。

import SwiftUI
import AVKit

class CameraPreviewView: UIView {
    private var captureSession: AVCaptureSession

    init(session: AVCaptureSession) {
        self.captureSession = session
        super.init(frame: .zero)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override class var layerClass: AnyClass {
        AVCaptureVideoPreviewLayer.self
    }

    var videoPreviewLayer: AVCaptureVideoPreviewLayer {
        return layer as! AVCaptureVideoPreviewLayer
    }

    override func didMoveToSuperview() {
        super.didMoveToSuperview()

        if nil != self.superview {
            self.videoPreviewLayer.session = self.captureSession
            self.videoPreviewLayer.videoGravity = .resizeAspectFill
            //Setting the videoOrientation if needed
            self.videoPreviewLayer.connection?.videoOrientation = .landscapeRight
        }
    }
}

struct CameraPreviewHolder: UIViewRepresentable {
    var captureSession: AVCaptureSession
    
    func makeUIView(context: UIViewRepresentableContext<CameraPreviewHolder>) -> CameraPreviewView {
        CameraPreviewView(session: captureSession)
    }

    func updateUIView(_ uiView: CameraPreviewView, context: UIViewRepresentableContext<CameraPreviewHolder>) {
    }

    typealias UIViewType = CameraPreviewView
}

用法:

您可以将您在相机管理器 class 中创建的 session 传递给CameraPreviewHolder并将其添加到您的 SwiftUI 视图。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM