관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] AVFoundation, Camera 다루는 방법 본문

iOS 응용 (swift)

[iOS - swift] AVFoundation, Camera 다루는 방법

jake-kim 2021. 1. 26. 01:55

핵심 타입

  • AVCaptureSession
  • AVCaptureDevice
  • AVCaptureDeviceInput, AVCapturePhotoOutput
  • AVCaptureVideoPreviewLayer

AVCaptureSession

  • 개념: 세션이란 입력에서 출력 장치로의 데이터 흐름을 제어하는데 사용
  • 해상도 설정: captureSession.sessionPreset
captureSession = AVCaptureSession()
captureSession.sessionPreset = .photo // set resoultion

AVCaptureDevice

  • 개념: 말 그래로 "capture"하는 물리적인 device를 참조하는 데이터형
guard let backCamera = AVCaptureDevice.default(for: AVMediaType.video) else {
    return
}
captureDevice = backCamera

AVCaptureDeviceInput, AVCapturePhotoOutput

  • AVCaptureDeviceInput: device의 입력
  • AVCapturePhotoOutput: device의 출력
  • DeviceInput으로 input을 참조, PhotoOutput으로 output을 참조하여 captureSession에 등록
do {
    let input = try AVCaptureDeviceInput(device: backCamera)
    stillIamgeOutput = AVCapturePhotoOutput()
    if captureSession = captureSession.canAddInput(input), captureSession.canAddOutput(stillImageOutput) {
        captureSession.addInput(input)
        captureSession.addOutput(stillImageOutput)
        // 여기에서 preview 세팅하는 함수 호출
    }
} catch {
    print(error.localizedDescription)
}

AVCaptureVideoPreviewLayer

  • 개념: input, output이 설정된 AVCaptureSession의 객체를 받아서 미리보기 화면의 정보를 갖는 Layer 데이터형
  • Layer이므로 따로 UIView를 만들어서 이곳에 부착하는 형태로 사용
private func setupLivePreview() {
    // previewLayer 세팅
    videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
    videoPreviewLayer.videoGravity = .resizeAspectFill
    videoPreviewLayer.connection?.videoOrientation = .portrait
    
    // UIView객체인 preView 위 객체 입힘
    preView.layer.insertSublayer(videoPreviewLayer, at: 0)  // 맨 앞(0번쨰로)으로 가져와서 보이게끔 설정
    DispatchQueue.main.async { in
        self.videoPreviewLayer.frame = self.preView.bounds
    }
        
    // preview까지 준비되었으니 captureSession을 시작하도록 설정
    
}
  • captureSession에 시작을 알림
private func startCaptureSession() { 
    DispatchQueue.global(qos: .userInitiated).async {
        self.captureSession.startRunning()
    }
}

기능 구현

  • focus
  • 카메라 전환 (앞, 뒤)
  • flash 버튼

Focus

  • preView에서 tap한 위치를 찾아내어 그 좌표를 focus시키는 방식
  • tap 이벤트 바인딩
preView.rx.tapGesture()
    .asDriverOnErrorNever()
    .drive(onNext: { [weak self] (gesture) in
        self?.tapFocus(gesture)
    }).disposed(by: bag)
  • focus : captureDevice를 가지고 focus 처리
private func tapFocus(_ sender: UITapGestureRecognizer) {
    if (sender.state == .ended) {
        let thisFocusPoint = sender.location(in: preView)
        focusAnimationAt(thisFocusPoint)

        let focus_x = thisFocusPoint.x / preView.frame.size.width
        let focus_y = thisFocusPoint.y / preView.frame.size.height

        if (captureDevice!.isFocusModeSupported(.autoFocus) && captureDevice!.isFocusPointOfInterestSupported) {
            do {
                try captureDevice?.lockForConfiguration()
                captureDevice?.focusMode = .autoFocus
                captureDevice?.focusPointOfInterest = CGPoint(x: focus_x, y: focus_y)

                if (captureDevice!.isExposureModeSupported(.autoExpose) && captureDevice!.isExposurePointOfInterestSupported) {
                    captureDevice?.exposureMode = .autoExpose;
                    captureDevice?.exposurePointOfInterest = CGPoint(x: focus_x, y: focus_y);
                 }

                captureDevice?.unlockForConfiguration()
            } catch {
                print(error)
            }
        }
    }
}
  • focus 받은 부분 애니메이션 처리
fileprivate func focusAnimationAt(_ point: CGPoint) {
    let focusView = UIImageView(image: UIImage(named: "aim"))
    focusView.center = point
    preView.addSubview(focusView)

    focusView.transform = CGAffineTransform(scaleX: 2, y: 2)

    UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
        focusView.transform = CGAffineTransform(scaleX: 0.85, y: 0.85)
    }) { (success) in
        UIView.animate(withDuration: 0.15, delay: 1, options: .curveEaseInOut, animations: {
            focusView.alpha = 0.0
        }) { (success) in
            focusView.removeFromSuperview()
        }
    }
}

카메라 전환

  • 새로운 device를 생성한 후 captureSession에 등록
private func switchCamera(captureSession: AVCaptureSession?) {
    captureSession?.beginConfiguration()
    let currentInput = captureSession?.inputs.first as? AVCaptureDeviceInput
    captureSession?.removeInput(currentInput!)

    let newCameraDevice = currentInput?.device.position == .back ? camera(with: .front) : camera(with: .back)
    let newVideoInput = try? AVCaptureDeviceInput(device: newCameraDevice!)
    captureSession?.addInput(newVideoInput!)
    captureSession?.commitConfiguration()
}

private func camera(with position: AVCaptureDevice.Position) -> AVCaptureDevice? {
    let devices = AVCaptureDevice.devices(for: AVMediaType.video)
    return devices.filter { $0.position == position }.first
}
  • flashMode의 flag값을 가지고 있다가, 사진을 찍을 때, AVCapturePhotoSetting의 객체를 가지고 stillImageOutput에 설정
func didTapBtnTakePicture(stillImageOutput: AVCapturePhotoOutput) {
    let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
    settings.flashMode = flashMode.value == .off ? .off : .on
    stillImageOutput.capturePhoto(with: settings, delegate: self)
}
Comments