|
|
|
import Foundation
|
|
|
|
import AVFoundation
|
|
|
|
import SessionUtilitiesKit
|
|
|
|
|
|
|
|
@objc
|
|
|
|
protocol CameraManagerDelegate : AnyObject {
|
|
|
|
|
|
|
|
func handleVideoOutputCaptured(sampleBuffer: CMSampleBuffer)
|
|
|
|
}
|
|
|
|
|
|
|
|
final class CameraManager : NSObject {
|
|
|
|
private let captureSession = AVCaptureSession()
|
|
|
|
private let videoDataOutput = AVCaptureVideoDataOutput()
|
|
|
|
private let videoDataOutputQueue
|
|
|
|
= DispatchQueue(label: "CameraManager.videoDataOutputQueue", qos: .userInitiated, attributes: [], autoreleaseFrequency: .workItem)
|
|
|
|
private let audioDataOutput = AVCaptureAudioDataOutput()
|
|
|
|
private var isCapturing = false
|
|
|
|
weak var delegate: CameraManagerDelegate?
|
|
|
|
|
|
|
|
private var videoCaptureDevice: AVCaptureDevice?
|
|
|
|
private var videoInput: AVCaptureDeviceInput?
|
|
|
|
|
|
|
|
func prepare() {
|
|
|
|
Log.debug("[CameraManager] Preparing camera.")
|
|
|
|
addNewVideoIO(position: .front)
|
|
|
|
}
|
|
|
|
|
|
|
|
private func addNewVideoIO(position: AVCaptureDevice.Position) {
|
|
|
|
if let videoCaptureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: position),
|
|
|
|
let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice), captureSession.canAddInput(videoInput) {
|
|
|
|
captureSession.addInput(videoInput)
|
|
|
|
self.videoCaptureDevice = videoCaptureDevice
|
|
|
|
self.videoInput = videoInput
|
|
|
|
}
|
|
|
|
if captureSession.canAddOutput(videoDataOutput) {
|
|
|
|
captureSession.addOutput(videoDataOutput)
|
|
|
|
videoDataOutput.videoSettings = [ kCVPixelBufferPixelFormatTypeKey as String : Int(kCVPixelFormatType_32BGRA) ]
|
|
|
|
videoDataOutput.setSampleBufferDelegate(self, queue: videoDataOutputQueue)
|
|
|
|
guard let connection = videoDataOutput.connection(with: AVMediaType.video) else { return }
|
|
|
|
connection.videoOrientation = .portrait
|
|
|
|
connection.automaticallyAdjustsVideoMirroring = false
|
|
|
|
connection.isVideoMirrored = (position == .front)
|
|
|
|
} else {
|
|
|
|
Log.info("[CameraManager] Couldn't add video data output to capture session.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func start() {
|
|
|
|
guard !isCapturing else { return }
|
|
|
|
|
|
|
|
// Note: The 'startRunning' task is blocking so we want to do it on a non-main thread
|
|
|
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
|
|
Log.debug("[CameraManager] Starting camera.")
|
|
|
|
self?.isCapturing = true
|
|
|
|
self?.captureSession.startRunning()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func stop() {
|
|
|
|
guard isCapturing else { return }
|
|
|
|
|
|
|
|
// Note: The 'stopRunning' task is blocking so we want to do it on a non-main thread
|
|
|
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
|
|
Log.debug("[CameraManager] Stopping camera.")
|
|
|
|
self?.isCapturing = false
|
|
|
|
self?.captureSession.stopRunning()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func switchCamera() {
|
|
|
|
guard let videoCaptureDevice = videoCaptureDevice, let videoInput = videoInput else { return }
|
|
|
|
stop()
|
|
|
|
if videoCaptureDevice.position == .front {
|
|
|
|
captureSession.removeInput(videoInput)
|
|
|
|
captureSession.removeOutput(videoDataOutput)
|
|
|
|
addNewVideoIO(position: .back)
|
|
|
|
} else {
|
|
|
|
captureSession.removeInput(videoInput)
|
|
|
|
captureSession.removeOutput(videoDataOutput)
|
|
|
|
addNewVideoIO(position: .front)
|
|
|
|
}
|
|
|
|
start()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension CameraManager : AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate {
|
|
|
|
|
|
|
|
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
|
|
|
|
guard connection == videoDataOutput.connection(with: .video) else { return }
|
|
|
|
delegate?.handleVideoOutputCaptured(sampleBuffer: sampleBuffer)
|
|
|
|
}
|
|
|
|
|
|
|
|
func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
|
|
|
|
Log.debug("[CameraManager] Frame dropped.")
|
|
|
|
}
|
|
|
|
}
|