导入 XuXuCheck 工程(iOS 干化学尿液试纸分析 App)
从桌面工程复制而来,作为本仓库的 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。
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user