Files
ios/Xuxucheck/XuXuCheck/Camera/CameraManager.swift
T
admin d6aeede2bc 导入 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。
2026-09-23 14:17:31 +08:00

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
}
}