Custom Camera 정리

김세영·2022년 1월 14일
0

출처
AVFoundation - Apple Developer
AVCam: Building a Camera App
https://guides.codepath.com/ios/Creating-a-Custom-Camera-View

AVFoundation

비디오, 오디오, 마이크 입력 등과 같은 기능을 구현하기 위해서는
AVFoundation(Audio Video Foundation) 을 import해야 한다.

AVCaptureSession

AVCaptureSession은 캡처 작업을 관리하고,
입력 장치에서부터 캡처할 때 까지의 데이터 flow를 관리한다고 한다.

  • 해당 객체를 생성해서 입력(오디오, 비디오)을 캡처할 수 있다.
  • .sessionPreset 프로퍼티로 출력에 대한 품질 등을 조절할 수 있다.
  • startRunning으로 flow를 시작할 수 있고,
    stopRunning으로 flow를 멈출 수 있다.
    • startRunning은 시간이 걸리는 blocking call이므로,
      메인 큐가 멈추지 않도록 GCD를 사용해야 한다.
  • canAddInput(_:), addInput(_:), canAddOutput(_:), addOutput(_:)
    세션에 AVCaptureInput / Ouptut을 추가할 수 있는지 확인하고, 추가하는 메서드.
    반드시 canAdd...(_:)을 거친 후 add...(_:) 메서드를 호출해야 한다.

AVCaptureDevice

Capture Session에 대한 입력을 제공하고,
하드웨어 별로 캡처 기능을 제어할 수 있는 기능을 제공한다고 한다.

  • 기기의 설정을 변경하려면 먼저 lockForConfiguration()을,
    변경이 끝나면 unlockForConfiguration()을 호출한다.
  • init(for: .video)builtInWideAngleCamera를 받아올 수 있다.

AVCaptureDeviceInput

캡처하려고 하는 장치에서 Capture Session으로 데이터를 제공하는 입력이라고 한다.

AVCapturePhotoOutput

정지 이미지, Live Photo 및 기타 사진 워크플로우의 캡쳐 출력이라고 한다.

  • 여러 is...Enabled 프로퍼티를 통해 Live Photo, 고해상도 캡처 기능 등을 선택하여
    렌더 파이프라인을 재구성한 후, 기능을 사용할 수 있다. (startRunning() 호출 전에 변경)
  • 세션 실행 중 프로퍼티를 변경하면 캡처 렌더 파이프라인이 중단된다.

AVCaptureVideoPreviewLayer

Core Animation Layer(CALayer)의 하위 클래스로, 캡처된 비디오를 표시하는 레이어라고 한다.

  • 입력 장치에서 캡처된 비디오를 표시하는 레이어
  • 다음과 같이 capture session과 연계해서 사용한다.
let previewLayer = AVCaptureVideoPreviewLayer()
previewLayer.session = captureSession
view.layer.addSublayer(previewLayer)
  • videoGravity 프로퍼티로 bounds 안에서 비디오가 표시되는 방법을 정할 수 있다.
    기본은 resizeAspect
  • 캡처 세션과 연결되면 캡처 세션이 암시적으로 적절한 AVCaptureInput.Port 객체를 연결한다.
  • connection?.videoOrientation으로 화면 회전을 조정할 수 있다.

AVCapturePhotoSettings

단일 사진 캡처의 요청에 사용되는 설정 및 기능이라고 한다.

  • 객체를 생성하고 구성한 뒤
    AVCapturePhotoOutput capturePhoto(with:delegate:) 메서드에 전달한다.
  • 캡처 세션에 설정값이 유효하지 않아도, 모든 설정을 조합하여 전달할 수 있다.
  • 전달된 setting의 uniqueId가 전에 전달된 setting의 것과 같다면 예외가 발생한다.

카메라 앱

앱에 카메라를 띄우고, 사진 찍기 버튼을 누르면 해당 사진을 UIImage로 획득하는 과정을 구현해 보았다.

import UIKit
import AVFoundation

class ViewController: UIViewController {

    @IBOutlet weak var previewView: UIView!

    var captureSession: AVCaptureSession!
    var stillImageOutput: AVCapturePhotoOutput!
    var previewLayer: AVCaptureVideoPreviewLayer!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        // AVCaptureSession 인스턴스 생성 및 preset 변경
        captureSession = AVCaptureSession()
        captureSession.sessionPreset = .medium
        
        // builtInWideAngleCamera를 획득
        guard let backCamera = AVCaptureDevice.default(for: .video) else {
            print("Fail to call back camera.")
            return
        }

        do {
            let input = try AVCaptureDeviceInput(device: backCamera)
            stillImageOutput = AVCapturePhotoOutput()

            if captureSession.canAddInput(input) && captureSession.canAddOutput(stillImageOutput) {
                captureSession.addInput(input)
                captureSession.addOutput(stillImageOutput)

                setUpLivePreview()
            }
        } catch {
            print(error.localizedDescription)
        }
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // 세션 정지
        self.captureSession.stopRunning()
    }

    func setUpLivePreview() {
        // 캡처 비디오를 표시할 레이어
        previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)

        previewLayer.videoGravity = .resizeAspect
        videoPreviewLayer.connection?.videoOrientation = .portrait
        previewView.layer.addSublayer(previewLayer)
        
        // startRunning이 blocking call이므로 GCD 사용
        DispatchQueue.global(qos: .userInitiated).async {
            // 세션 시작
            self.captureSession.startRunning()
            
            // UI 변경을 위해 main queue 접근
            DispatchQueue.main.async {
                self.previewLayer.frame = self.previewView.bounds
            }
        }
    }

    @IBAction func didTakePhoto() {
        // 호출될 때 마다 다른 세팅을 주어야 하기 때문에 메서드 안에서 생성
        let settings = AVCapturePhotoSettings(
format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
        
        // 아래에 AVCapturePhotoCaptureDelegate를 채택
        stillImageOutput.capturePhoto(with: settings, delegate: self)
    }
}

extension ViewController: AVCapturePhotoCaptureDelegate {

    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        guard let imageData = photo.fileDataRepresentation() else { return }
        let image = UIImage(data: imageData)
        print(image)
    }
}
profile
초보 iOS 개발자입니다ㅏ

0개의 댓글