출처
AVFoundation - Apple Developer
AVCam: Building a Camera App
https://guides.codepath.com/ios/Creating-a-Custom-Camera-View
비디오, 오디오, 마이크 입력 등과 같은 기능을 구현하기 위해서는
AVFoundation(Audio Video Foundation) 을 import해야 한다.
AVCaptureSession
은 캡처 작업을 관리하고,
입력 장치에서부터 캡처할 때 까지의 데이터 flow를 관리한다고 한다.
.sessionPreset
프로퍼티로 출력에 대한 품질 등을 조절할 수 있다.startRunning
으로 flow를 시작할 수 있고,stopRunning
으로 flow를 멈출 수 있다.startRunning
은 시간이 걸리는 blocking call이므로,canAddInput(_:), addInput(_:), canAddOutput(_:), addOutput(_:)
AVCaptureInput / Ouptut
을 추가할 수 있는지 확인하고, 추가하는 메서드.canAdd...(_:)
을 거친 후 add...(_:)
메서드를 호출해야 한다.Capture Session에 대한 입력을 제공하고,
하드웨어 별로 캡처 기능을 제어할 수 있는 기능을 제공한다고 한다.
lockForConfiguration()
을,unlockForConfiguration()
을 호출한다.init(for: .video)
로 builtInWideAngleCamera를 받아올 수 있다.캡처하려고 하는 장치에서 Capture Session으로 데이터를 제공하는 입력이라고 한다.
정지 이미지, Live Photo 및 기타 사진 워크플로우의 캡쳐 출력이라고 한다.
is...Enabled
프로퍼티를 통해 Live Photo, 고해상도 캡처 기능 등을 선택하여startRunning()
호출 전에 변경)Core Animation Layer(CALayer
)의 하위 클래스로, 캡처된 비디오를 표시하는 레이어라고 한다.
let previewLayer = AVCaptureVideoPreviewLayer()
previewLayer.session = captureSession
view.layer.addSublayer(previewLayer)
videoGravity
프로퍼티로 bounds 안에서 비디오가 표시되는 방법을 정할 수 있다.resizeAspect
AVCaptureInput.Port
객체를 연결한다.connection?.videoOrientation
으로 화면 회전을 조정할 수 있다.단일 사진 캡처의 요청에 사용되는 설정 및 기능이라고 한다.
AVCapturePhotoOutput capturePhoto(with:delegate:)
메서드에 전달한다.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)
}
}