AVCaptureSession 과 AVCaptureVideoDataOutput은 생성시 별도 설정이 없으므로, 미리 생성해 둔다.
Input의 경우 device 객체가 필요하므로, device 생성 이후에 설정.
output의 경우 device에 따라 픽셀 포맷이 변경되므로, 관련 프로퍼티 추가.
var captureSession: AVCaptureSession = .init()
var videoDataOutput: AVcaptureVideoDataOutput = .init()
var currentDevice: AVCaptureDevice?
var preview: AVCaptureVideoPreviewLayer?
var deviceFormat: AVCaptureDevice.Format?
var preferredOutputPixelFormat: FourCharCode = 0
var sampleQueue: DispatchQueue = DispatchQueue.global(qos: .userInteractive)
세션 설정
캡처 세션의 기본 설정은 preset 으로 제공되는데, AVCaptureSession.Preset 참조
동적으로 입출력 포맷을 설정하기를 원하는 경우 .inputPriority 프리셋 사용
비디오만 처리할 것이라 usesApplicationAudioSession 은 false로 선언했는데.. ios7 이후에는 차이가 없으므로 굳이 설정할 필요가 없을 듯 싶다.
captureSession.beginConfiguration()
captureSession.sessionPreset = .inputPriority
captureSession.usesApplicationAudioSession = false
// input 설정 및 추가
// output 설정 및 추가
captureSession.commitConfiguration()
디바이스 설정
디바이스에 따라 AVCaptureDevice.Format 이 변경되므로, format 에 따른 해상도, fps 설정을 진행한다.
참고) 카메라 전환 시 디바이스 설정부터 다시 수행해야 한다.
let position = AVCaptureDevice.Position.front
let currentDevice: AVCaptureDevice?
currentDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: position)
guard let device = currentDevice else { return }
// width, height
let formats: [AVCaptureDevice.Format] = device.formats
let targetWidth = 640
let targetHeight = 480
var selectedFormat: AVCaptureDevice.Format?
var currentDiff = INT_MAX
for format in formats {
let dimension: CMVideoDimensions = CMVideoFormatDescriptionGetDimensions(format.formatDescription)
let pixelFormat: FourCharCode = CMFormatDescriptionGetMediaSubType(format.formatDescription)
let diff = abs(targetWidth - dimension.width) + abs(targetHeight - dimension.height)
if diff < currentDiff {
selectedFormat = format
currentDiff = diff
} else if diff == currentDiff && pixelFormat == preferredOutputPixelFormat {
selectedFormat = format
}
}
self.deviceFormat = selectedFormat
// fps 설정
let maxFrameRate: Float64 = 0.0
for fpsRange in selectedFormat.videoSupportedFrameRateRanges {
maxFrameRate = fmax(maxFrameRate, fpsRange.maxFrameRate)
}
let fps = Int(maxFrameRate)
do {
try device.lockForConfiguration()
device.activeFormat = selectedFormat
device.activeVideoMinFrameDuration = CMTimeMake(value: 1, timescale: Int32(fps))
device.unlockForConfiguration()
} catch {
}
입력
디바이스에 해당하는 AVCaptureDeviceInput 객체를 생성하고, 캡처세션에 추가한다.
guard let input: AVCaptureDeviceInput = try? AVCaptureDeviceInput(device: device) else {
return
}
let inputs = captureSession.inputs
for old in inputs {
captureSession.removeInput(old)
}
if captureSession.canAddInput(input) {
captureSession.addInput(input)
} else {
}
출력 데이터 포맷, delegate 설정
픽셀포맷을 설정하고, 캡처세션에 추가한다.
Pixel Format Identifiers | Apple Developer Documentation
주요 픽셀 포맷 값
CV420YpCbCr8BiPlanarVideoRange : 875704438
CV420YpCbCr8BiPlanarFullRange : 875704422
CV32BGRA : 1111970369
CV32ARGB : 32
VideoDataOutput의 경우 i420과 BGRA만 지원하고, ARGB는 지원하지 않음.
// 사용할 픽셀포맷
let pixelFormats: Set<OSType> = Set([kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
kCVPixelFormatType_32BGRA,
kCVPixelFormatType_32ARGB
])
// 출력에서 지원하는 픽셀포맷
let availablePixelFormats = NSMutableOrderedSet(array: videoDataOutput.availableVideoPixelFormatTypes)
availablePixelFormats.intersectSet(pixelFormats)
let pixelFormat = availablePixelFormats.firstObject as? OSType ?? 0
// 우선순위 설정
self.preferredOutputPixelFormat = pixelFormat
// 디바이스 설정시 선택한 포맷과 비교
if let format = self.deviceFormat {
var mediaSubType: FourCharCode = CMFormatDescriptionGetMediaSubType(format.formatDescription)
if supportedPixelFormats.contains(mediaSubType) {
if mediaSubType != preferredOutputPixelFormat {
self.preferredOutputPixelFormat = mediaSubType
}
} else {
mediaSubType = self.preferredOutputPixelFormat
}
}
// 출력 설정
let settings: [String: Any] = [
String(kCVPixelBufferMetalCompatibilityKey): true,
String(kCVPixelBufferPixelFormatTypeKey): NSNumber(value: self.preferredOutputPixelFormat),
]
videoDataOutput.videoSettings = settings
videoDataOutput.alwaysDiscardsLateVideoFrames = true
videoDataOutput.setSampleBufferDelegate(self, queue: sampleQueue)
// 출력 등록
if captureSession.canAddOutput(videoDataOutput) {
captureSession.addOutput(videoDataOutput)
}
만약 출력된 데이터를 별도로 수정하거나 다른 타입으로 전환하는 등의 픽셀버퍼를 핸들링하는 경우 plane 타입의 픽셀포맷 보다는 단순 바이트 배열인 BGRA 포맷을 사용하는게 좋다.
설정 변경 및 캡처 시작
// 출력 미러모드 등 변경이 필요한 경우
captureSession.beginConfiguration()
if let connector = videoDataOutput.connection(with: .video) {
if connector.isVideoMirrorinigSupported {
if !isFrontCamera {
connector.videoOrientation = .landscapeRight
connector.isVideoMirrored = false
} else {
connector.videoOrientation = .landscapeLeft
if isMirror {
connectior.isVideoMirrored = true
connector.videoOrientation = .landscapeRight
} else {
connector.isVideoMirrored = false
}
}
}
}
captureSession.commitConfiguration()
// 캡처 시작
captureSession.startRunning()
캡처 중지
captureSession.stopRunning()
AVCaptureVideoDataOutputSampleBufferDelegate
func captureOutput(_ output: AVCaptureOutput,
didOutput sampleBuffer: CMSampleBuffer,
from connection: AVCaptureConnection) {
if CMSampleBufferGetNumSamples(sampleBuffer) != 1 ||
!CMSampleBufferIsValid(sampleBuffer) ||
!CMSampleBufferDataIsReady(sampleBuffer) {
return
}
let cvPixelBuffer: CVPixelBuffer? = CMSampleBufferGetImageBuffer(sampleBuffer)
guard let pixelBuffer = cvPixelBuffer else {
return
}
let time = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer))
let nanoTime = time * 1_000_000_000
}
'프로그래밍 > iOS,macOS' 카테고리의 다른 글
[Combine] 콜백 기반 여러 처리 결과를 배열로 받기 (0) | 2022.03.20 |
---|---|
[SwiftUI] 오디오 레벨 에니메이션, Shape (0) | 2022.01.06 |
dataTaskPublisher 재시도 및 출력 타입 변경 (0) | 2021.10.16 |
Framework SPM 배포 (0) | 2021.09.24 |
URLSession.DataTaskPublisher (0) | 2021.09.17 |
collection view 에서 load more 처리 (0) | 2021.07.20 |
UIPanGestureRecognizer 슬라이드 다운 뷰 (0) | 2021.07.01 |
UITextView 사이즈 조정 및 글자 제한, placeholder (0) | 2021.06.30 |
UITextView 자동 높이 (0) | 2021.06.03 |
오디오유닛 레벨 계산 (0) | 2021.05.01 |