본문 바로가기
아이폰 개발/ios 개념&튜토리얼

ios AVCaptureSession 예제

by 인생여희 2023. 3. 20.

import UIKit
import AVFoundation

class ViewController: UIViewController {

    /// 캡쳐동작 구성. 입력에서 출력으로 데이터 흐름 제어
    let captureSession = AVCaptureSession()
    
    var videoDevice: AVCaptureDevice!
    
    var videoInput: AVCaptureDeviceInput!
    
    var audioInput: AVCaptureDeviceInput!
    
    var videoOutput: AVCaptureMovieFileOutput!
    
    /// 카메라 입력 화면
    lazy var previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
    
    /// 저장 경로
    var outputURL: URL?
    
    /// 화면 방향
    var deviceOrientation: AVCaptureVideoOrientation = .portrait
    
    lazy var recordBtn: UIButton = {
        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("녹화", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.backgroundColor = .white
        button.addTarget(self, action: #selector(recordAction), for: .touchUpInside)
        return button
    }()
    
    //MARK: viewDidLoad
    override func viewDidLoad() {
        super.viewDidLoad()
        
        /// 미리보기 뷰 셋팅
        self.setupPreviewLayer()
        
        /// 버튼 셋팅
        self.setupComponentLayout()
        
        /// 녹화 품질 설정
        self.captureSession.sessionPreset = .high
        
        self.videoDevice = bestDevice(in: .back)

        self.setupSession()
    }
    
    
    //MARK: 녹화버튼
    @objc func recordAction(sender: UIButton!) {
       
        if self.videoOutput.isRecording {
            
            print("videoOutput.isRecording (o)")
            self.stopRecording()
            self.recordBtn.setTitle("녹화", for: .normal)
            
        } else {
            print("videoOutput.isRecording (x)")
            
            self.startRecording()
            self.recordBtn.setTitle("정지", for: .normal)
        }
    }
    
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        if !captureSession.isRunning {
            
            DispatchQueue.global(qos: .background).async {
                self.captureSession.startRunning()
            }
        }
    }
    
    func setupComponentLayout(){
        
        self.view.addSubview(recordBtn)

        recordBtn.centerXAnchor.constraint(equalTo:view.centerXAnchor).isActive = true
        recordBtn.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50).isActive = true
        recordBtn.heightAnchor.constraint(equalToConstant: 80).isActive = true
        recordBtn.widthAnchor.constraint(equalToConstant: 80).isActive = true
        
    }

    //MARK: PreviewLayer
    func setupPreviewLayer(){
        
        self.previewLayer.bounds = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height)
        self.previewLayer.position = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY)
        self.previewLayer.videoGravity = .resizeAspectFill
        
        self.view.layer.addSublayer(self.previewLayer)
        
    }
    
    
    //MARK: 캡쳐 디바이스
    /// device 별로 카메라 타입의 사용여부가 다르기 때문에 사용가능한 타입 리턴
    func bestDevice(in position: AVCaptureDevice.Position) -> AVCaptureDevice {
        
        var deviceTypes: [AVCaptureDevice.DeviceType]!
        
        
        if #available(iOS 11.1, *){
            deviceTypes = [.builtInTrueDepthCamera , .builtInDualCamera , .builtInWideAngleCamera]
        }else{
            deviceTypes = [.builtInDualCamera , .builtInWideAngleCamera]
        }
        
        let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: .video, position: .unspecified)
        
        let devices = discoverySession.devices
        guard !devices.isEmpty else { fatalError("Missing capture devices.") }
            
        return devices.first { device in
            device.position == position
        }!
    }

    
    //MARK: 세션셋팅
    func setupSession(){
        
        do {
            /// 세션 구성의 시작
            captureSession.beginConfiguration()
            
            
            ///비디오 장치에 대한 입력을 만들어서 세션에 추가
            videoInput = try AVCaptureDeviceInput(device: videoDevice!)
            if captureSession.canAddInput(videoInput){
                captureSession.addInput(videoInput)
            }
            
            ///오디오 장치에 대한 입력을 만들어서 세션에 추가
            let audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)!
            audioInput = try AVCaptureDeviceInput(device: audioDevice)
            if captureSession.canAddInput(audioInput){
                captureSession.addInput(audioInput)
            }
            
            
            ///비디오 및 오디오를 파일로 출력하기 위한 인스턴스를 만들어서 세션에 추가
            videoOutput = AVCaptureMovieFileOutput()
            if captureSession.canAddOutput(videoOutput){
                captureSession.addOutput(videoOutput)
            }
            
            ///세션 구성의 완료
            captureSession.commitConfiguration()
            
        } catch let error as NSError {
            print("\(error) , \(error.localizedDescription)")
        }
        
    }
    
    //MARK: 레코딩 시작
    private func startRecording(){
        
        let connection = videoOutput.connection(with: AVMediaType.video)
        
        //orientation을 설정해야 가로/세로 방향에 따른 레코딩 출력이 잘나옴
        if (connection?.isVideoOrientationSupported)! {
            connection?.videoOrientation = self.deviceOrientation
        }
        
        
        let device = videoInput.device
        if device.isSmoothAutoFocusSupported{
            do {
                try device.lockForConfiguration()
                device.isSmoothAutoFocusEnabled = false
                device.unlockForConfiguration()
            } catch {
                print("Error setting configuration: \(error)")

            }
        }
        
        outputURL = tempURL()
        videoOutput.startRecording(to: outputURL!, recordingDelegate: self)
    }
    
    
    
    //MARK: 레코딩 종료
    private func stopRecording(){
        
        if videoOutput.isRecording{
            videoOutput.stopRecording()
        }
    }
    
}



//MARK: delegate
extension ViewController: AVCaptureFileOutputRecordingDelegate{
    
    /// 레코딩 시작시 호출
    func fileOutput(_ output: AVCaptureFileOutput, didStartRecordingTo fileURL: URL, from connections: [AVCaptureConnection]) {
        print("레코딩이 시작되었습니다.")
    }
        
    /// 레코딩 종료시 호출
    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
        
        if error != nil {
            print("Error recording movie: \(error!.localizedDescription)")

        }else{
            let videoRecored = outputURL! as URL
            UISaveVideoAtPathToSavedPhotosAlbum(videoRecored.path, nil, nil, nil)
            print("레코딩이 저장완료.")
        }
        
    }
    
    
    
}



extension ViewController{

    func tempURL() -> URL? {
        
        let directory = NSTemporaryDirectory() as NSString
        
        if directory != ""{
            let path = directory.appendingPathComponent(NSUUID().uuidString + ".mp4")
            return URL(fileURLWithPath: path)
        }
        return nil
    }
    
}


/*
 참고 : https://jintaewoo.tistory.com/43
 */

 

AVCaptureSession.zip
0.04MB

 

 

참고 : https://jintaewoo.tistory.com/43