Skip to content

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

컴포넌트 연동

PassthroughSubject 이벤트

  • savedVideoInfo: 녹화 완료 시 파일 URL 전달
let savedVideoInfo = PassthroughSubject<URL, Never>()

// 녹화 완료 시
videoSaved(url: outputFileURL)
// 내부에서
savedVideoInfo.send(url)

CameraView 연동

@StateObject private var cameraManager = CameraManager()

// 녹화 완료 이벤트 구독
.onReceive(cameraManager.savedVideoInfo) { url in
    // 촬영 완료 후 저장된 파일 URL을 받아 clipEditView로 이동
    self.clipUrl = url
    self.navigateToEdit = true
}

BoundingBoxManager 연동

private let boundingBoxManager = BoundingBoxManager()

// 비디오 데이터 출력 델리게이트 설정
videoOutput.setSampleBufferDelegate(boundingBoxManager, queue: videoDataOutputQueue)

// 바운딩 박스 업데이트 콜백
var onMultiBoundingBoxUpdate: (([CGRect]) -> Void)?

Clone this wiki locally