[英]how to paint on live camera using Camera overlay view : issues in code
[英]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
编写视频/音频时捕获的
建议:
我的建议是创建一个基于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.