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
*/
'아이폰 개발 > ios 개념&튜토리얼' 카테고리의 다른 글
ios AVPlayerLayer 사용해보기 (0) | 2023.03.18 |
---|---|
ios UIBezierPath 연습 2 (파이차트 그리기) (0) | 2023.03.08 |
ios UIBezierPath 연습 1 (원그리기, 호그리기,선그리기) (0) | 2023.03.07 |
ios 코어 그래픽 1 (1) | 2023.03.04 |
ios 아날로그 시계 만들기 3 (0) | 2023.03.03 |