d6aeede2bc
从桌面工程复制而来,作为本仓库的 Xuxucheck/ 子目录。 为控制仓库体积做了两处删减: - 移除 Frameworks/opencv2.xcframework 下的两个 visionOS 切片 (xros-arm64、xros-arm64-simulator,合计约 106 MB),并同步删掉该 xcframework 内 Info.plist 里对应的两条 AvailableLibraries 记录, 只保留 ios-arm64 与 ios-arm64_x86_64-simulator。 - 移除全部 .DS_Store 与 xcuserdata/。 已用 xcodebuild 从本目录完整编译验证:BUILD SUCCEEDED, ProcessXCFramework 以 --platform ios 成功选中框架,产物含 3699 个 cv:: 符号 (OpenCV 确实链入),嵌入 framework 3 个,与移除无用 pod 之后的状态一致。 仓库约 206 MB,其中 OpenCV 203 MB、其余源码与依赖约 3 MB。
147 lines
4.0 KiB
Swift
147 lines
4.0 KiB
Swift
//
|
|
// CameraManager.swift
|
|
// XuXuCheck
|
|
//
|
|
// Created by henseycai on 2025/6/25.
|
|
//
|
|
|
|
import AVFoundation
|
|
import CoreImage
|
|
|
|
|
|
class CameraManager: NSObject {
|
|
|
|
private let captureSession = AVCaptureSession()
|
|
private var deviceInput: AVCaptureDeviceInput?
|
|
private var videoOutput: AVCaptureVideoDataOutput?
|
|
private let systemPreferredCamera = AVCaptureDevice.default(for: .video)
|
|
|
|
private var sessionQueue = DispatchQueue(label: "video.preview.session")
|
|
|
|
private var addToPreviewStream: ((CGImage) -> Void)?
|
|
|
|
lazy var previewStream: AsyncStream<CGImage> = {
|
|
AsyncStream { continuation in
|
|
addToPreviewStream = { cgImage in
|
|
continuation.yield(cgImage)
|
|
}
|
|
}
|
|
}()
|
|
|
|
private var isAuthorized: Bool {
|
|
get async {
|
|
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
|
|
|
// Determine if the user previously authorized camera access.
|
|
var isAuthorized = status == .authorized
|
|
|
|
// If the system hasn't determined the user's authorization status,
|
|
// explicitly prompt them for approval.
|
|
if status == .notDetermined {
|
|
isAuthorized = await AVCaptureDevice.requestAccess(for: .video)
|
|
}
|
|
|
|
return isAuthorized
|
|
}
|
|
}
|
|
|
|
override init() {
|
|
super.init()
|
|
|
|
Task {
|
|
await configureSession()
|
|
//await startSession()
|
|
}
|
|
|
|
}
|
|
public func StartCapture(){
|
|
Task{
|
|
await startSession()
|
|
}
|
|
}
|
|
public func StopCapture(){
|
|
Task{
|
|
await stopSession()
|
|
}
|
|
}
|
|
|
|
private func startSession() async {
|
|
guard await isAuthorized else { return }
|
|
captureSession.startRunning()
|
|
}
|
|
|
|
private func stopSession() async {
|
|
guard await isAuthorized else { return }
|
|
captureSession.stopRunning()
|
|
}
|
|
|
|
private func configureSession() async {
|
|
guard await isAuthorized,
|
|
let systemPreferredCamera,
|
|
let deviceInput = try? AVCaptureDeviceInput(device: systemPreferredCamera)
|
|
else { return }
|
|
|
|
captureSession.beginConfiguration()
|
|
|
|
defer {
|
|
self.captureSession.commitConfiguration()
|
|
}
|
|
|
|
let videoOutput = AVCaptureVideoDataOutput()
|
|
|
|
videoOutput.setSampleBufferDelegate(self, queue: sessionQueue)
|
|
|
|
guard captureSession.canAddInput(deviceInput) else {
|
|
return
|
|
}
|
|
|
|
guard captureSession.canAddOutput(videoOutput) else {
|
|
return
|
|
}
|
|
|
|
captureSession.addInput(deviceInput)
|
|
captureSession.addOutput(videoOutput)
|
|
|
|
//For a vertical orientation of the camera stream
|
|
videoOutput.connection(with: .video)?.videoRotationAngle = 90
|
|
}
|
|
|
|
|
|
|
|
|
|
private func rotate(by angle: CGFloat, from connection: AVCaptureConnection) {
|
|
guard connection.isVideoRotationAngleSupported(angle) else { return }
|
|
connection.videoRotationAngle = angle
|
|
}
|
|
|
|
}
|
|
|
|
extension CameraManager: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
|
|
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
|
|
guard let currentFrame = sampleBuffer.cgImage else {
|
|
print("Can't translate to CGImage")
|
|
return
|
|
}
|
|
addToPreviewStream?(currentFrame)
|
|
}
|
|
|
|
}
|
|
|
|
|
|
extension CMSampleBuffer {
|
|
var cgImage: CGImage? {
|
|
let pixelBuffer: CVPixelBuffer? = CMSampleBufferGetImageBuffer(self)
|
|
guard let imagePixelBuffer = pixelBuffer else { return nil }
|
|
return CIImage(cvPixelBuffer: imagePixelBuffer).cgImage
|
|
}
|
|
}
|
|
|
|
extension CIImage {
|
|
var cgImage: CGImage? {
|
|
let ciContext = CIContext()
|
|
guard let cgImage = ciContext.createCGImage(self, from: self.extent) else { return nil }
|
|
return cgImage
|
|
}
|
|
}
|