-
Notifications
You must be signed in to change notification settings - Fork 0
CameraManager
bishoe01 edited this page Jul 23, 2025
·
5 revisions
카메라 기능을 담당 매니저 클래스
AVFoundation 프레임워크를 기반 / 카메라 세션 관리, 비디오 녹화, 줌 조절, 카메라 전환 등의 기능을 제공합니다.
- 후면 카메라 중 최고 성능 카메라 자동 선택 (가상 카메라 우선)
- 비디오 데이터 출력 설정 및 바운딩 박스 매니저 연동
- 1080p 60fps 최적 포맷 자동 설정
configureFrameRate - 오디오 입력 자동 추가
-
지원카메라타입
가상카메라
- TripleCamera
- DualWideCamera
- DualCamera
물리 카메라(단일렌즈)
- WideAngleCamera
- 동영상 녹화 시작/정지
- 녹화 완료 시 파일URL
PassthroughSubject로 전달
savedVideoInfo.send(url)
- 문서 디렉토리에 타임스탬프 기반 파일명으로 저장
let videoName = "video_\(Date().timeIntervalSince1970).mp4"
- 전면/후면 카메라 전환
switchCamera - 줌 배율 조정
setZoomScale(기본 배율 2.0 적용) - 터치 포커스 지원
focusAtPoint - 토치 모드 제어
setTorchMode(off/on/auto 지원) - 카메라별 줌 스케일 저장 및 복원
- 카메라 접근 권한 요청 및 상태 확인
requestAndCheckPermissions - 오디오 권한 별도 확인
checkAudioPermission - 권한 승인 시 자동 카메라 설정
setUpCamera
// requestAndCheckPermissions
case .authorized:
checkAudioPermission()var session: AVCaptureSession
var videoDeviceInput: AVCaptureDeviceInput
let movieOutput: AVCaptureMovieFileOutput
let videoOutput: AVCaptureVideoDataOutput
let savedVideoInfo = PassthroughSubject<URL, Never>()
private let boundingBoxManager = BoundingBoxManager()
@Published var isRecording: Bool
@Published var currentZoomScale: CGFloat
@Published var torchMode: TorchMode = .off
private var backCameraZoomScale: CGFloat = 1.0카메라 설정 setUpCamera()
if let device = findBestBackCamera()
// 최적의 후면 카메라를 찾아 세션에 연결하고 출력 설정
videoDeviceInput = try AVCaptureDeviceInput(device: device)
session.addInput(videoDeviceInput)
session.addOutput(movieOutput)
// 프레임레이트 설정
configureFrameRate(for: device)
// 오디오 입력 추가
let audioInput = try AVCaptureDeviceInput(device: audioDevice)configureFrameRate(_:) 프레임레이트 최적화
// 1080p 이하 해상도에서 60fps 지원하는 최고 해상도 포맷 검색
for format in device.formats {
let dimensions = CMVideoFormatDescriptionGetDimensions(format.formatDescription)
let currentResolution = dimensions.width * dimensions.height
if currentResolution <= 2073600 { // 1080p (1920x1080) 제한
for range in format.videoSupportedFrameRateRanges {
if range.maxFrameRate >= 60 // 60fps 지원 확인
}
}
}
// 60fps로 고정
device.activeVideoMinFrameDuration = CMTime(value: 1, timescale: 60)
device.activeVideoMaxFrameDuration = CMTime(value: 1, timescale: 60)여기서 최적이란??
let deviceTypes: [AVCaptureDevice.DeviceType] = [
.builtInTripleCamera,
.builtInDualWideCamera,
.builtInDualCamera,
.builtInWideAngleCamera
]가용 가능한 카메라 리스트 중 devices.first를 통해서 배열 순서가 앞일수록 우선순위가 높음
findBestBackCamera() 최적의 후면 카메라 선택
private func findBestBackCamera() -> AVCaptureDevice? {
let deviceTypes: [AVCaptureDevice.DeviceType] = [
.builtInTripleCamera,
.builtInDualWideCamera,
.builtInDualCamera,
.builtInWideAngleCamera
]
let discoverySession = AVCaptureDevice.DiscoverySession(
deviceTypes: deviceTypes,
mediaType: .video,
position: .back
)
return discoverySession.devices.first
}requestAndCheckPermissions(): 권한 요청 및 확인
// 카메라 권한 상태에 따라 분기 처리
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized: setUpCamera()
case .notDetermined: AVCaptureDevice.requestAccess(for: .video) { ... }
}startRecording() 녹화 시작
// 문서 디렉토리에 타임스탬프 기반 파일명녹화 시작
let videoName = "video_\(Date().timeIntervalSince1970).mp4"
let videoURL = documentsPath.appendingPathComponent(videoName)
movieOutput.startRecording(to: videoURL, recordingDelegate: self)stopRecording() 녹화 정지
// 현재 녹화 중인 경우에만 정지
guard isRecording else { return }
movieOutput.stopRecording()switchCamera(to:) 카메라 전환
// 기존 입력 제거 후 새로운 카메라로 교체
session.removeInput(existingInput)
let newDevice = position == .back ? findBestBackCamera() : frontCamera
videoDeviceInput = try AVCaptureDeviceInput(device: newDevice)setZoomScale(_:) 줌 배율 설정
// 줌 배율에 2.0 기본 배율 적용
let zoomFactorToSet = scale * 2.0
// 디바이스 지원 범위 내에서 줌 조정
let clampedZoomFactor = max(minZoom, min(zoomFactorToSet, maxZoom))
device.videoZoomFactor = clampedZoomFactor
// 카메라별 줌 스케일 저장
if device.position == .back {
backCameraZoomScale = scale
}setTorchMode(_:) 토치 모드 설정 (플래쉬가 지속적으로 켜져있는것을 토치모드라고 정의함)
// 토치 지원 여부 확인 후 토치 모드 설정
if device.hasTorch && device.isTorchAvailable {
switch mode {
case .off: device.torchMode = .off
case .on: device.torchMode = .on
case .auto: device.torchMode = .auto
}
}focusAtPoint(_:) 터치 포커스 조정
// 터치 포인트로 포커스 및 노출 조정
device.focusPointOfInterest = point
device.exposurePointOfInterest = point
device.focusMode = .autoFocus
device.exposureMode = .autoExpose-
savedVideoInfo: 녹화 완료 시 파일 URL 전달
let savedVideoInfo = PassthroughSubject<URL, Never>()
// 녹화 완료 시
videoSaved(url: outputFileURL)
// 내부에서
savedVideoInfo.send(url)@StateObject private var cameraManager = CameraManager()
// 녹화 완료 이벤트 구독
.onReceive(cameraManager.savedVideoInfo) { url in
// 촬영 완료 후 저장된 파일 URL을 받아 clipEditView로 이동
self.clipUrl = url
self.navigateToEdit = true
}private let boundingBoxManager = BoundingBoxManager()
// 비디오 데이터 출력 델리게이트 설정
videoOutput.setSampleBufferDelegate(boundingBoxManager, queue: videoDataOutputQueue)
// 바운딩 박스 업데이트 콜백
var onMultiBoundingBoxUpdate: (([CGRect]) -> Void)?