SwiftUI 에서 카메라를 사용할 수 있습니다
AVFoundation
AVFoundation 프레임워크는 기기의 input 을 처리하며 미디어를 처리할 수 있는 코드를 제공합니다.
- 카메라와 음성인식을 위한 Input을 설정해야합니다.
- input으로 설정된 장치으로 Preview 를 통해 Output 으로 변환되는 값을 사용자한테 미리 보여줄 수 있습니다.
- input을 설정함에 따라서 즉석에서 찍은 사진, 앨범 사진, 앨범 동영상을 가져올 수 있습니다.
AVCaptureDevice 로 input 설정하기
위와 같은 구조를 만들어 Preview 와 Output 결과를 받고 싶으면 먼저 Input을 설정해야합니다.
AVFoundation은 input 장치를 설정할 수 있도록 AVCaptureDevice 클래스를 제공하고 있습니다.
AVCaptureDevice는 어떤 타입의 input 장치로도 쓰일 수 있습니다. (카메라, 음성인식)
AVCaptureDevice 에서 제공하는 메서드
AVCaptureDevice는 다음과 같은 메서드들을 포함하여 기기에 접근 및 관리할 수 있습니다.
default(for: AVMediaType)
- 이 메서드는 지정된 미디어 타입에 대한 AVCaptureDevice (캡쳐 장치)객체를 반환합니다.
- .video 타입을 요청하면 기본 카메라 장치를 반환합니다.
- .audio 타입을 요청하면 기본 마이크 장치를 반환합니다.
var captureDevice: AVCaptureDevice?
viewData.captureDevice = AVCaptureDevice.default(for: AVMediaType.video)
requestAccess(for: AVMediaType)
- 이 메서드는 비동기적 타입 메서드로 사용자한테 해당 장치의 접근 권한을 요청합니다.
- for 에 들어가는 매개변수는 어떤 타입의 장치의 접근 권한을 요청할지 결정합니다.
let granted = await AVCaptureDevice.requestAccess(for: .audio) //AVMediaType
authorizationStatus(for: AVMediaType)
- 이 메서드는 접근 권한에 대한 상태를 반환합니다.
- for 에 들어가는 매개변수는 접근 권한을 확인할 장치를 넣습니다.
- notDetermined, restricted, denied, authorized 가 있습니다.
isSubjectAreaChangeMotitoringEnabled
- 사용자가 카메라를 이동하거나 새로운 객체가 프레임에 들어올 때를 감지할지 여부를 결정 또는 Bool 값을 반환합니다.
var captureDevice: AVCaptureDevice?
viewData.captureDevice?.isSubjectAreaChangeMonitoringEnabled = true
formats
- 단어 뜻 그대로 기기가 어떤 포맷을 지원하는지 배열로 반환합니다.
- 장치가 지원하는 다양한 해상도를 확인할 수 있습니다.
- 각 포맷에서 지원하는 프레임 속도를 확인할 수 있습니다.
- 비디오 포맷 세부 정보 접근 등이 가능합니다.
activateFormat
- 현재 캡처 장치에서 활성화된 포맷을 나타냅니다, 현재 캡처 세션에서 사용중인 해상도, 프레임 속도 등을 포함합니다.
lockForConfiguration, unlockForConfiguration
- 이 메서드는 AVCaptureDevice의 설정을 안전하게 변경하기 위해 사용됩니다. 이 메서드들을 사용하여 장치의 변경을 변경하는 동안 코드가 동시에 설정을 변경하지 못하도록 잠금을 걸어 데이터 무결성을 유지할 수 있습니다.
AVCaptureOutput 클래스로 output 사용하기
AVCaptureOutput 는 output 를 사용할 수 있도록 다음과 같은 여러 서브클래스들을 제공하고 있습니다.
AVCaptureVideoDataOutput: 영상의 프레임을 처리합니다.
AVCaptureAudioDataOutput: 오디오를 처리합니다.
AVCapturePhotoOutput: 영상에서 하나의 프레임을 처리합니다.
위와 같은 서브클래스들은 AVCapturePhotoCaptureDelegate 프로토콜을 준수하는 클래스에서 함께 사용됩니다.
최대 해상도 설정하기
maxPhotoDimensions:
AVCapturePhotoCaptureDelegate
이 delegate 프로토콜은 미디어의 Input과 Ouput의 흐름을 관리합니다
AVCapturePhotoCaptureDelegate 는 사진 챕쳐 세션의 결과를 처리하기 위한 프로토콜로 Delegate 메서드를 구현하여 캡처된 이미지 데이터를 처리할 수 있습니다.
capturePhoto(with:delegate:)
- AVCapturePhotoOutput 클래스의 메서드로, 사진을 캡처하는 기능을 제공합니다.
- 이 메서드는 특정 설정(AVCapturePhotoSettings)을 사용하여 사진을 촬영하고, 결과를 AVCapturePhotoCaptureDelegate 프로토콜을 준수하는 Delegate 객체에 전달합니다.
photoOutput(AVCapturePhotoOutput, didFinishProcessingPhoto: AVCapturePhoto, error: Error?)
- 이 메서드는 이지미가 캡처됐을 때 호출되는 메서드입니다.
- didFinishProcessingPhoto 매개변수는 캡처된 이미지에 대한 정보를 포함하고 있습니다.
AVCaptureSession
AVCaptureSession 는 input 에서 output 으로 변환되는 흐름을 컨트롤합니다. 이 클래스 인스턴스를 통해서 input 과 ouput 의 시작과 끝을 결정하는 메서드들을 다음과 같이 제공하고 있습니다.
addInput(AVCaptureInput)
- 이 메서드는 AVCaptureSession 에 원하는 input 장치를 주가해줍니다.
if let input = try? AVCaptureDeviceInput(device: device) {
viewData.captureSession?.addInput(input)
addOutput(AVCaptureOutput)
- 이 메서드는 AVCaptureSession 에 원하는 output 장치를 주가해줍니다.
startRunning()
- 이 메서드는 capture session 을 시작합니다.
stopRunning()
- 이 메서드는 capture session 을 멈춥니다.
AVCaptureVideoPreviewLayer
AVCaptureVideoPreviewLayer 는 input 장치로부터 캡처된 비디오를 사용자한테 미리 보여줍니다 (preview).
AVCaptureVideoPreviewLayer(session: AVCaptureSession)
- 이 생성자는 인스턴스를 만듭니다.
connection
- AVCaptureConnection 을 반환하며 이것은 capture session과 preview layer 사이의 연결을 의미합니다.
AVCaptureConnection
AVCaptureConnection 은 port와 데이터를 포함한 connection 정보를 관리합니다.
videoOrientation
- 이 프로퍼티는 현재 미디오의 회전 상태를 반환하거나 설정합니다.
- portrait, portraitUpsideDown, landscapeRight, landscapeLeft 가 있습니다
isVideoOrientationSupported
- 이 프로퍼티는 비디오가 회전 가능한지 여부를 나타내는 Bool 값을 반환합니다.
RotationCoordinator
SwiftUI에서 뷰의 회전(orientation) 변화를 감지하고, 이에 따라 동작을 조정할 수 있도록 도와주는 클래스입니다.
비디오를 회전시키려면 RotationCoordinator가 필요하며 이것은 AVCaptureDevice에 정의되어 있습니다.
AVCaptureDevice.RotationCoordinator(device: AVCaptureDevice, previewLayer: CALayer?)
- 이 생성자는 기기와 preview layer 를 위한 rotation coordinator 를 만듭니다.
videoRotationAngleForHorizonLevelPreview
- 이 프로퍼티는 기기의 물리적으로 어느정도 기울여야 preview layer에 회전이 적용되는지 각도를 반환합니다.
videoRotationAngleForHorizonLevelCaptur
- 이 프로퍼티는 기기의 물리적으로 어느정도 기울여야 이미지에 회전이 적용되는지 각도를 반환합니다.
이론은 여기까지 마무리하고 직접 코드를 보면서 파악하겠습니다.
코드로 파악하기
권한 요청하기
카메라를 사용하기 위해서는 가장 먼저 권한을 요청해야 합니다.
func getAuthorization() async {
let granted = await AVCaptureDevice.requestAccess(for: .audio)
await MainActor.run {
if granted {
self.prepareCamera()
} else {
print("Not Authorized")
}
}
}
카메라 준비하기
func prepareCamera() {
viewData.captureSession = AVCaptureSession()
viewData.captureDevice = AVCaptureDevice.default(for: AVMediaType.video)
if let _ = try? viewData.captureDevice?.lockForConfiguration() {
viewData.captureDevice?.isSubjectAreaChangeMonitoringEnabled = true
viewData.captureDevice?.unlockForConfiguration()
}
if let device = viewData.captureDevice {
if let input = try? AVCaptureDeviceInput(device: device) {
viewData.captureSession?.addInput(input)
viewData.stillImage = AVCapturePhotoOutput()
if viewData.stillImage != nil {
viewData.captureSession?.addOutput(viewData.stillImage!)
if let max = viewData.captureDevice?.activeFormat.supportedMaxPhotoDimensions.last {
viewData.stillImage?.maxPhotoDimensions = max
}
}
showCamera()
} else {
print("Not Authorized")
}
} else {
print("Not Authorized")
}
}
- AVCaptureSession 는 input 에서 output 으로 변환되는 흐름을 컨트롤을 관리해주는 Session 입니다.
- Session 을 통해서 input 와 output 을 설정할 수 있습니다.
- captureDevice 를 통해 어떤 미디어 타입을 사용할지 설정해줍니다.
- 설정되었으면 AVCaptureDevice 설정을 안전하게 할 수 있도록 lockForConfiguration 으로 잠그고 isSubjectAreaChangeMonitoringEnabled 를 true 로 바꿔 카메라의 프레임 이동, 변동을 감지할 수 있게 합니다.
- 설정이 완료되었으면 unlockForConfiguration 으로 잠금을 해지합니다.
- AVCapturePhotoOutput() 출력 클래스를 사용하여 input으로 들어오는 사진 데이터를 처리하는 관련 설정을 가능하게 합니다.
- stillImage 인스턴스로 받아 해당 인스턴스를 AVCaptureSession 의 output 으로 추가해줍니다.
- output으로 설정된 AVCapturePhotoOutput 을 통해 maxPhotoDimensions 로 캡처 장치의 최대 사진 크기를 설정합니다.
카메라에 찍히는 화면 확인하기
func showCamera() {
let previewLayer = cameraView.view.layer as? AVCaptureVideoPreviewLayer
previewLayer?.session = viewData.captureSession
if let device = viewData.captureDevice, let preview = previewLayer {
viewData.rotationCoordinator = AVCaptureDevice.RotationCoordinator(device: device, previewLayer: preview)
preview.connection?.videoRotationAngle = viewData.rotationCoordinator!.videoRotationAngleForHorizonLevelPreview
viewData.previewObservation = viewData.rotationCoordinator!.observe(\.videoRotationAngleForHorizonLevelPreview, changeHandler: { old, value in
preview.connection?.videoRotationAngle = self.viewData.rotationCoordinator!.videoRotationAngleForHorizonLevelPreview
})
}
Task(priority: .background) {
viewData.captureSession?.startRunning()
}
}
- AVCaptureVideoPreviewLayer 를 사용하면 카메라에 찍히는 화면을 확인할 수 있습니다.
- 화면에 보여주기 위해 session 을 AVCaptureSession 인스턴스로 설정해줍니다. (현재 session 에 영상을 사용하기 때문)
- 사용자한테 보여줄 화면의 회전을 설정하기 위해서 rotationCoordinator 를 설정합니다.
- RotationCoordinator 을 통해 현재 장치(카메라)와 PreviewLayer를 통해 화면에 찍히고 있는 회전 상태를 preview 의 connection.videoRotationAngle 에 설정합니다.
- KVO(Key-Value Observation) 설정으로 videoRotationAngleForHorizonLevelPreview 의 상태를 관찰하여 값이 변경될 때마다 Preview Layer 의 videoRotationAngle 를 업데이트합니다.
- 마지막으로 captureSession 의 startRunning 메서드를 통해 Session 에 구성된 설정들로 카메라 입력을 활성화 합니다.
카메라로 사진찍기
func takePicture() {
let settings = AVCapturePhotoSettings()
if let max = viewData.captureDevice?.activeFormat.supportedMaxPhotoDimensions.last {
settings.maxPhotoDimensions = max
}
viewData.stillImage?.capturePhoto(with: settings, delegate: self)
}
- AVCapturePhotoSettings() 생성자는 AVCapturePhotoSettings 객체를 만들어 포맷을 기본으로 설정합니다.
- maxPhotoDimensions: 찍히는 사진의 크기를 설정하거나 반환합니다.
- previewPhotoFormat: 딕셔너리 Key-Value 로 preview image 의 설정을 설정 또는 반환합니다.
- flashMode: 플래시 모드를 설정 또는 반환합니다. on, off, auto가 존재합니다.
- 사진을 찍기 위해서는 AVCapturePhotoOutput 의 capturePhoto 메서드를 실행합니다.
사진찍은 결과 처리하기
사진 찍은 결과를 처리하기 위해서는 Delegate 메서드를 사용해야 합니다.
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
let scale = scene?.screen.scale ?? 1
let orientationAngle = viewData.rotationCoordinator!.videoRotationAngleForHorizonLevelCapture
var imageOrientation: UIImage.Orientation!
switch orientationAngle {
case 90.0:
imageOrientation = .right
case 270.0:
imageOrientation = .left
case 0.0:
imageOrientation = .up
case 180.0:
imageOrientation = .down
default:
imageOrientation = .right
}
if let imageData = photo.cgImageRepresentation() {
picture = UIImage(cgImage: imageData, scale: scale, orientation: imageOrientation)
path = NavigationPath()
}
}
- didFinishProcessingPhoto 는 찍은 사진에 대한 데이터를 포함하고 있습니다.
- 찍은 사진을 cgImageRepresentation 을 사용하여 cgImage 로 변환해줍니다.
- rotationCoordinator 를 통해 회전 상태를 observe 하고 있기 때문에 회전 상태 값을 orientationAngle 로 받아 회전할지를 결정합니다.
'SwiftUI' 카테고리의 다른 글
SwiftUI - 소켓 통신으로 영상 전송받기 (0) | 2024.11.13 |
---|---|
SwiftUI - 소켓 통신 (5) | 2024.11.12 |
WidgetKit - UserDefault 로 위젯과 앱 데이터 공유하기 (0) | 2024.10.23 |
WidgetKit 에서 SwiftData 로 저장된 데이터 사용하기 (0) | 2024.10.22 |
SwftUI - ShareLink 공유 기능 사용하기, 공유 인터페이스 (1) | 2024.10.22 |