iOS 개발일기

[iOS] USB 카메라 연결(2) 본문

iOS

[iOS] USB 카메라 연결(2)

맨날 까먹으니 적어두자 2025. 2. 19. 19:43

이전 게시글에서는 외부 카메라를 유선으로 연결할 수 있는 방법과 연결했을 때 디바이스의 포맷을 확인하거나 연결 및 해제를 감지하는 방법에 대해서 설명했다.

 

 

2025.01.09 - [iOS] - [iOS] USB 카메라 연결(1)

 

이번 글에서는 연결된 외부 카메라의 영상을 iOS 기기에 출력하는 방법을 말하려고 한다. 그리고 프로젝트를 진행하면서 미리보기 화면을 출력하면서 외부 카메라의 영상이 기기의 화면 방향과 맞지 않아서 생겼던 문제에 대해서도 다룰 예정이다.

 

카메라 미리보기 화면 표시

외부 카메라의 화면을 내 기기에 표시하는 방법은 우리가 흔히 아는 AVCaptureSession을 통해 쉽게 구현이 가능하며, 보통 내장 카메라의 화면을 표시하는 방법과 동일하다. 

import UIKit
import AVFoundation

class ExternalCameraViewController: UIViewController {

    var externalCamera: AVCaptureDevice?
    var captureSession: AVCpatureSession?
    
    ///iOS 17이상 & USB-C 포트 또는 MFi 인증 기기 
    let discoverySession: AVCaptureDevice.DiscoverySession
    
    init() {
        discoverySession = AVCaptureDevice.DiscoverySession(
            deviceTypes: [.external], //외부 카메라 
            mediaType: .video,        //비디오 타입 장치
            position: .unspecified    //카메라 장치 위치(모든 위치/전면/후면)
        )
        
        super.init(nibName: nil, bundle: nil)
        
        externalCamera = discoverySession.devices.first
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews() 
        
        setupExternalCamera()
    }
    
    private func setupExternalCamera() {
        session = AVCaptureSession()
        
        guard let camera, let session else {return}
        do {
            let input = try AVCaptureDeviceInput(device: externalCamera)
            if session.canAddInput(input) {
                session.addInput(input)
            }
        } catch {
            print(error.localizedDescription)
        }
        
        let output = AVCapturePhotoOutput()
        if session.canAddOutput(output) {
            session.addOutput(output)
        }
        
        let previewLayer = AVCaptureVideoPreviewLayer(session: session)
        previewLayer.frame = view.bounds
        previewLayer.videoGravity = .resizeAspect //외부 카메라의 화면을 비율에 맞게 유지
        
        view.layer.addSublayer(previewLayer)
        
        DispatchQueue.global().async {
            session.startRunning()
        }
    } 
}

(외부 카메라를 연결한 상태에서 뷰 컨트롤러로 이동한다는 가정이며, 뷰 컨트롤러에 접속 후 카메라 연결을 감지하고 세션을 표시하고 싶다면 이전 게시글에서 외부 카메라 연결/해제 감지 방법을 확인할 수 있습니다.)

 

 

화면 회전과 좌우반전 문제

그런데 막상 화면을 표시해보면 미리보기 화면이 왼쪽 또는 오른쪽으로 회전되어 있거나 거울처럼 좌우가 반대로 되어있는 경우가 있다. (프로젝트 개발 당시에는 미리보기 화면이 왼쪽으로 90도 회전 및 좌우 반전 현상이 있었다.)

 

 

이런 현상은 왜 일어나는 걸까?

 

먼저 미리보기 화면이 왼쪽으로 90도 회전되어 나타나는 현상의 원인은 iOS와 외부 카메라 센서가 제공하는 기본적인 화면 방향이 서로 달랐기 때문에 발생하는 문제였다. (개발 중이었던 앱은 세로 모드(Portrait)로 설정했던 반면 외부 카메라는 기본적으로 가로 모드(Landscape)였기 때문에 서로 방향이 맞지 않아서 회전된 화면이 표시되었던 것이었다.)

 

이 문제는 iOS의 방향과 외부 카메라의 방향을 파악하고 둘 중 하나의 방향을 한쪽의 방향에 맞추어주면 해결할 수 있다.

 

 

해결 방법

 

1. iOS 기기 방향을 외부 카메라에 맞추기

외부 카메라가 오른쪽으로 90도 회전되어 있다면 Landscape Right, 왼쪽으로 90도 회전되어 있다면 Landscape Left로 맞추면 된다.

출처 : https://github.com/mixi-inc/iOSTraining/blob/master/Swift/pages/day2/1-3_Supporting-Multiple-Interface-Orientations.md

 

iOS 기기의 현재 방향과 외부 카메라에서 기본으로 설정되어 있는 방향을 파악한 후에 뷰 컨트롤러가 지원하는 방향을`supportedInterfaceOrientations` 파라미터로 지정해주고 `setNeedsUpdateOfSupportedInterfaceOrientations()` 메서드를 호출해 줌으로써 화면 회전이 될 수 있도록 해주어야 한다.

override func supportedInterfaceOrientations: UIInterfaceOrientationMask { 
    return .landscape 
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    setNeedsUpdateOfSupportedInterfaceOrientations()
}

 

 

2. 외부 카메라의 방향을 iOS 기기에 맞추기

외부 카메라의 방향을 변경하기 위해서는 카메라의 화면을 표시해주는 `AVCaptureVideoPreviewLayer`를 제어해주어야 한다. 이 방법은 iOS의 기기의 방향에 맞게 `videoOrientation` 또는 `videoRotationAngle`을 변경해주면 쉽게 적용할 수 있다.

if #available(iOS 17.0, *) {
    // 회전 각도 및 방향 설정
    // 0  : 기본 상태
    // 90 : 오른쪽으로 90도 회전
    // 180: 상하 반전
    // 270: 왼쪽으로 90도 회전
    previewLayer.connection?.videoRotationAngle = 90
} else {
    previewLayer.connection?.videoOrientation = .portrait
}

 

현재 기기의 방향과 외부 카메라의 방향을 확인해서 설정해주면 된다. (connection의 값의 경우에는 previewLayer를 생성할 때 연결된 session의 AVCaptureConnection을 생성하여 사용하게 된다.)

 

 

3. 미러링 문제 해결

화면을 회전시켜 정상적으로 보이도록 설정하고 보니 이제는 화면이 좌우반전이 되어 표시되고 있었다. 흔히 우리가 알고있는 미러링이 되어있는 건데 외부 카메라의 경우에는 미러링이 지원되는지 확인한 후에 속성을 변경해주어야 한다. 

//비디오 미러링 지원 여부 확인
if previewLayer.connection?.isVideoMirroringSupported {

    //자동 미러링 조정 비활성화
    previewLayer.connection?.automaticallyAdjustsVideoMirroring = false 
    
    //미러링 비활성화
    previewLayer.connection?.isVideoMirrored = false
}

 

 

주의할 점이 있는데 미러링을 비활성화하기 위해서 먼저 외부 카메라의 미러링 지원 여부를 확인하고 자동 미러링 조정하는 설정을 비활성화를 먼저 시켜주어야 한다. 그렇지 않으면 앱을 실행하면 충돌이 일어나서 앱이 강제 종료 된다. (경험담) 다른 속성들도 변경하고 싶다면 위와 같은 순서대로 먼저 변경하고자 하는 속성이 지원하는지 확인 후 자동 조정 변수가 존재한다면 비활성화를 해준 다음 진행해주어야 앱이 강제 종료되지 않는다. 

 

 

PreviewLayer 미러링의 경우에는 우리가 보는 화면에 표시되는 미리보기 화면에서만 미러링되어 표시되는 것이다. (?) 이말이 헷갈릴수도 있는데 미러링이 되어 있는 상태에서 캡처를 진행하면 캡처된 이미지는 좌우반전되지 않은 정상적인 이미지가 캡처가 될 수 있다. 그 이유는 미리보기(AVCaptureVideoPreviewLayer)만 미러링이 활성화되어 있기 때문이다. 

 

만약 기기가 미러링이 비활성화되어 있는 상태에서 캡처를 했을 때 좌우가 반전된 캡처가 되었다면 출력 객체에 미러링이 활성화되어 있다고 볼 수 있다. 이 상황에서는 AVCapturePhotoOutput의 미러링을 해제해주면 캡처한 후 이미지가 정상적으로 표시된다.

 

if let connection = output.connection(with: .video), connection.isVideoMirroringSupported {
    connection.automaticallyAdjustsVideoMirroring = false 
    connection.isVideoMirrored = false
}

 

AVCaptureVideoPreviewLayer와 AVCapturePhotoOutput은 같은 디바이스 정보를 기반으로 AVCaptureConnection객체를 생성하지만 각 객체의 특성에 맞도록 서로 다른 값을 가진다. 그렇기 때문에 미리보기와 관련된 미러링을 한다고 해서 캡처도 같이 적용되지는 않는다. 이게 외부 카메라가 어떻게 지원하느냐에 따라 다 다르겠지만 이 원리만 알아둔다면 다음에는 쉽게 해결할 것 같다.

 

 

 

'iOS' 카테고리의 다른 글

[iOS] USB 카메라 연결(1)  (0) 2025.01.09
[iOS] 화면 캡처 감지 및 방지 방법  (0) 2024.12.31
[iOS] 오픈소스 라이선스 표시 및 라이브러리  (0) 2024.12.04
[iOS] Realm 사용법  (0) 2024.11.24
[iOS] SwiftGen 사용법  (1) 2024.10.20