AVFoundation Switching Cameras Slow (Connected To Sample Buffer) - ios

override func viewDidLoad() {
super.viewDidLoad()
...
setupDevice()
setupInputOutput()
}
View did load starts the setup chain
func setupDevice() {
let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], mediaType: AVMediaType.video, position: AVCaptureDevice.Position.unspecified)
let devices = deviceDiscoverySession.devices
for device in devices {
if device.position == AVCaptureDevice.Position.back {
backCamera = device
setupCorrectFramerate(currentCamera: backCamera!)
}
else if device.position == AVCaptureDevice.Position.front {
frontCamera = device
setupCorrectFramerate(currentCamera: frontCamera!)
}
}
currentCamera = cameraModeIsBack ? backCamera : frontCamera
}
var deviceInput: AVCaptureDeviceInput?
let videoOutput = AVCaptureVideoDataOutput()
func setupInputOutput() {
do {
let captureDeviceInput = try AVCaptureDeviceInput(device: currentCamera!)
deviceInput = captureDeviceInput
captureSession.sessionPreset = AVCaptureSession.Preset.hd1280x720
if captureSession.canAddInput(deviceInput!) {
captureSession.addInput(deviceInput!)
}
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "sample buffer delegate", attributes: []))
if captureSession.canAddOutput(videoOutput) {
captureSession.addOutput(videoOutput)
}
captureSession.startRunning()
if(firstTime){
setupMetal()
}
firstTime = false
toggleCamerButton.isEnabled = true
} catch {
print(error)
}
}
On a high level I find the device inputs, setup their framerate and setup the capture session, (the buffer output gets processed to a MTKView).
The issue is when I change cameras. It's either snappy and isntant or becomes very slow and freezes for 4-5 seconds before responding.
The logic I use to change the cameras:
func updateCameras(){
DispatchQueue.global().async {
self.captureSession.stopRunning()
self.captureSession.removeInput(self.deviceInput!)
self.currentCamera = self.cameraModeIsBack ? self.backCamera : self.frontCamera
self.changeCameras()
}
}
func changeCameras(){
do {
let captureDeviceInput = try AVCaptureDeviceInput(device: self.currentCamera!)
self.deviceInput = captureDeviceInput
if self.captureSession.canAddInput(self.deviceInput!) {
self.captureSession.addInput(self.deviceInput!)
}
self.captureSession.startRunning()
DispatchQueue.main.async {
self.toggleCamerButton.isEnabled = true
}
} catch {
print(error)
}
}
I've revised it as much as I can. Switching cameras is essentially
Stopping the camera session
removing the input
adding the input
starting the session
I have no idea why on earth it's so variable (works perfectly most of the time on my iPhone X while on my iPhone 6 it's always slow).

I suspect the calls to manipulate the cameraSession should be done on the main thread. Try changing updateCameras() to use DispatchQueue.main.async() instead of DispatchQueue.global().async
Like this:
func updateCameras(){
DispatchQueue.main.async {
self.captureSession.stopRunning()
self.captureSession.removeInput(self.deviceInput!)
self.currentCamera = self.cameraModeIsBack ? self.backCamera : self.frontCamera
self.changeCameras()
}
}
With that change the call to DispatchQueue.main.async() in your changeCameras() function should no longer be needed

Related

AVCapturePhoto SemanticSegmentationMatte nil without audio input?

When I add audio input to capture session, photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) callback returns semantic segmentation mattes properly. Without audio input, returned mattes are nil. Is it possible to avoid adding audio input and requesting user to give permission for microphone in order to get mattes?
// MARK: - Session
private func setupSession() {
captureSession = AVCaptureSession()
captureSession?.sessionPreset = .photo
setupInputOutput()
setupPreviewLayer(view)
captureSession?.startRunning()
}
// MARK: - Settings
private func setupCamera() {
settings = AVCapturePhotoSettings()
let supportsHEVC = AVAssetExportSession.allExportPresets().contains(AVAssetExportPresetHEVCHighestQuality)
settings = supportsHEVC ? AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.hevc]) : AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
settings!.flashMode = .auto
settings!.isHighResolutionPhotoEnabled = true
settings!.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: settings!.__availablePreviewPhotoPixelFormatTypes.first ?? NSNumber()]
settings!.isDepthDataDeliveryEnabled = true
settings!.isPortraitEffectsMatteDeliveryEnabled = true
if self.photoOutput?.enabledSemanticSegmentationMatteTypes.isEmpty == false {
settings!.enabledSemanticSegmentationMatteTypes = self.photoOutput?.enabledSemanticSegmentationMatteTypes ?? [AVSemanticSegmentationMatte.MatteType]()
}
settings!.photoQualityPrioritization = self.photoQualityPrioritizationMode
}
private func setupInputOutput() {
photoOutput = AVCapturePhotoOutput()
guard let captureSession = captureSession else { return }
guard let photoOutput = photoOutput else { return }
do {
captureSession.beginConfiguration()
captureSession.sessionPreset = .photo
let devices = self.videoDeviceDiscoverySession.devices
currentDevice = devices.first(where: { $0.position == .front && $0.deviceType == .builtInTrueDepthCamera })
guard let videoDevice = currentDevice else {
captureSession.commitConfiguration()
return
}
videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice)
if captureSession.canAddInput(videoDeviceInput) {
captureSession.addInput(videoDeviceInput)
} else {
captureSession.commitConfiguration()
return
}
currentDevice = AVCaptureDevice.default(for: .audio)
captureDeviceInput = try AVCaptureDeviceInput(device: currentDevice!)
if captureSession.canAddInput(captureDeviceInput) {
captureSession.addInput(captureDeviceInput)
} else {
captureSession.commitConfiguration()
return
}
} catch {
errorMessage = error.localizedDescription
print(error.localizedDescription)
captureSession.commitConfiguration()
return
}
if captureSession.canAddOutput(photoOutput) {
captureSession.addOutput(photoOutput)
photoOutput.isHighResolutionCaptureEnabled = true
photoOutput.isLivePhotoCaptureEnabled = photoOutput.isLivePhotoCaptureSupported
photoOutput.isDepthDataDeliveryEnabled = photoOutput.isDepthDataDeliverySupported
photoOutput.isPortraitEffectsMatteDeliveryEnabled = photoOutput.isPortraitEffectsMatteDeliverySupported
photoOutput.enabledSemanticSegmentationMatteTypes = photoOutput.availableSemanticSegmentationMatteTypes
photoOutput.maxPhotoQualityPrioritization = .balanced
}
captureSession.commitConfiguration()
}
private func setupPreviewLayer(_ view: UIView) {
self.cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession ?? AVCaptureSession())
self.cameraPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
self.cameraPreviewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation.portrait
self.cameraPreviewLayer?.frame = view.frame
view.layer.insertSublayer(self.cameraPreviewLayer ?? AVCaptureVideoPreviewLayer(), at: 0)
}
I was not able to return semantic segmentation mattes (SSM) at all with/without setting up audio input. I am currently developing on an iPhone X. After struggling for some time, I asked Apple the question on a 1-1 lab session during WWDC2021. I was told that the API would only make portrait effect matte visible to my device. iPhone 11 and above would be able to get skin, teeth and hair. The new glasses ssm that they snuck in recently without announcing requires iPhone 12.

How to switch camera using AVFoundation

I have implemented the preview camera using AVFoundation, its working fine. But I have a hard time to switch the camera back and front. I have added a switch button at the bottom bar. By default, its the back camera, I want to switch it to front. How can I do that?
class FifteenSecsViewController: UIViewController, AVCaptureFileOutputRecordingDelegate {
#IBOutlet weak var camPreview: UIView!
let captureSession = AVCaptureSession()
let movieOutput = AVCaptureMovieFileOutput()
var previewLayer: AVCaptureVideoPreviewLayer!
var activeInput: AVCaptureDeviceInput!
var outputURL: URL!
override func viewDidLoad() {
super.viewDidLoad()
if setupSession() {
setupPreview()
startSession()
}
self.switchCameraButton.addTarget(self, action: #selector(switchButtonTapped), for: .touchUpInside)
}
func setupSession() -> Bool {
captureSession.sessionPreset = AVCaptureSession.Preset.high
// Setup Camera
let camera: AVCaptureDevice?
camera = AVCaptureDevice.default(for: .video)
do {
let input = try AVCaptureDeviceInput(device: camera!)
if captureSession.canAddInput(input) {
captureSession.addInput(input)
activeInput = input
}
} catch {
print("Error setting device video input: \(error)")
return false
}
// Setup Microphone
let microphone = AVCaptureDevice.default(for: .audio)
do {
let micInput = try AVCaptureDeviceInput(device: microphone!)
if captureSession.canAddInput(micInput) {
captureSession.addInput(micInput)
}
} catch {
print("Error setting device audio input: \(error)")
return false
}
// Movie output
let seconds : Int64 = 3
let maxDuration = CMTime(seconds: Double(seconds),
preferredTimescale: 1)
movieOutput.maxRecordedDuration = maxDuration
if captureSession.canAddOutput(movieOutput) {
captureSession.addOutput(movieOutput)
}
return true
}
func setupPreview() {
// Configure previewLayer
previewLayer = AVCaptureVideoPreviewLayer(session:
captureSession)
previewLayer.frame = camPreview.bounds
previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
camPreview.layer.addSublayer(previewLayer)
}
//MARK:- Camera Session
func startSession() {
if !captureSession.isRunning {
videoQueue().async {
self.captureSession.startRunning()
}
}
}
#objc func switchButtonTapped(){
// what to write here??
}
}
Function switchButtonTapped is an actionTarget of UIButton. If I add this code in this button:
#objc func switchButtonTapped(){
if setupSession() {
setupPreview()
startSession()
}
}
Camerapreview screen shows a white screen and got stuck.
Try this code:
func switchCamera() {
session?.beginConfiguration()
let currentInput = session?.inputs.first as? AVCaptureDeviceInput
session?.removeInput(currentInput!)
let newCameraDevice = currentInput?.device.position == .back ? getCamera(with: .front) : getCamera(with: .back)
let newVideoInput = try? AVCaptureDeviceInput(device: newCameraDevice!)
session?.addInput(newVideoInput!)
session?.commitConfiguration()
}
func getCamera(with position: AVCaptureDevice.Position) -> AVCaptureDevice? {
guard let devices = AVCaptureDevice.devices(for: AVMediaType.video) as? [AVCaptureDevice] else {
return nil
}
return devices.filter {
$0.position == position
}.first
}
To begin create a device input for the front camera:
let frontDevice: AVCaptureDevice? = {
for device in AVCaptureDevice.devices(for: AVMediaType.video) {
if device.position == .front {
return device
}
}
return nil
}()
lazy var frontDeviceInput: AVCaptureDeviceInput? = {
if let _frontDevice = self.frontDevice {
return try? AVCaptureDeviceInput(device: _frontDevice)
}
return nil
}()
Then in your switchButtonTapped, if there is a front camera you can do the switch between the front and the ones:
func switchButtonTapped() {
if let _frontDeviceInput = frontDeviceInput {
captureSession.beginConfiguration()
if let _currentInput = captureSession.inputs.first as? AVCaptureDeviceInput {
captureSession.removeInput(_currentInput)
let newDeviceInput = (_currentInput.device.position == .front) ? activeInput : _frontDeviceInput
captureSession.addInput(newDeviceInput!)
}
captureSession.commitConfiguration()
}
}
If you need more details, don't hesitate.

torchMode blinks at the start and immediately goes off

I don't understand why torch is not continuously on.
It blinks at the start and immediately goes off.
I did lock and unlock as well.
I added part of my code which I think is relevent with the problem here.
override func viewDidLoad() {
super.viewDidLoad()
initCamera()
}
func initCamera(){
captureSession.sessionPreset = .medium
guard let device = AVCaptureDevice.default(for: .video) else {
print("no camera")
return
}
try? device.lockForConfiguration()
device.activeVideoMaxFrameDuration = CMTimeMake(1, 30)
if device.hasTorch && device.isTorchAvailable && !device.isTorchActive{
do {
try device.setTorchModeOn(level: 1.0)
device.torchMode = .on
} catch {
print("Torch could not be used")
}
} else {
print("Torch is not available")
}
if device.isFocusModeSupported(.continuousAutoFocus){
device.focusMode = .continuousAutoFocus
}
device.unlockForConfiguration()
guard let captureInput = try? AVCaptureDeviceInput(device: device) else {return;}
if captureSession.canAddInput(captureInput) {
captureSession.addInput(captureInput)
}
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
view.layer.addSublayer(previewLayer)
let videoOutput = AVCaptureVideoDataOutput()
let queue = DispatchQueue(label: "queue image delegate", attributes: .concurrent)
videoOutput.setSampleBufferDelegate(self, queue: queue)
if captureSession.canAddOutput(videoOutput)
{
captureSession.addOutput(videoOutput)
}
captureSession.startRunning()
}

AVFoundation camera crashing while switching to front camera (refreshing camera)

I am a new bee to iOS, working on simple application with swift, In that I need a custom camera. I am using AVFoundation but the app is showing black screen for long time, then it's loading the camera. here is my code
func prepareCamera() {
captureSession.sessionPreset = AVCaptureSessionPresetPhoto
if frontCam{
if let availableDevices = AVCaptureDeviceDiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaTypeVideo, position: .front).devices {
captureDevice = availableDevices.first
DispatchQueue(label: "prepare").async {
self.beginSession()
}
}
}else{
if let availableDevices = AVCaptureDeviceDiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaTypeVideo, position: .back).devices {
captureDevice = availableDevices.first
beginSession()
}
}
}
#IBAction func switchCameraBtnClicked(_ sender: Any) {
frontCam = !frontCam
prepareCamera()
}
func beginSession () {
do {
let captureDeviceInput = try AVCaptureDeviceInput(device: captureDevice)
if let inputs = captureSession.inputs as? [AVCaptureDeviceInput] {
for input in inputs {
captureSession.removeInput(input)
}
}
if captureSession.inputs.isEmpty {
captureSession.addInput(captureDeviceInput)
}
}catch {
print(error.localizedDescription)
}
if let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) {
self.previewLayer = previewLayer
// self.view.layer.addSublayer(self.previewLayer)
self.view.layer.insertSublayer(self.previewLayer, at: 0)
self.previewLayer.frame = self.view.layer.frame
captureSession.startRunning()
let dataOutput = AVCaptureVideoDataOutput()
dataOutput.videoSettings = [(kCVPixelBufferPixelFormatTypeKey as NSString):NSNumber(value:kCVPixelFormatType_32BGRA)]
dataOutput.alwaysDiscardsLateVideoFrames = true
if captureSession.canAddOutput(dataOutput) {
captureSession.addOutput(dataOutput)
}
captureSession.commitConfiguration()
let queue = DispatchQueue(label: "com.graymatics.customcamera")
dataOutput.setSampleBufferDelegate(self, queue: queue)
}
}
Please correct me if the code is not proper.
Finally found the solution
here is my code:
override func viewDidDisappear(_ animated: Bool) {
self.stopCaptureSession()
}
func stopCaptureSession () {
self.captureSession.stopRunning()
if let inputs = captureSession.inputs as? [AVCaptureDeviceInput] {
for input in inputs {
self.captureSession.removeInput(input)
}
}
}
session needs to be stopped while moving on from the current view controller.

How can i improve my Custom Camera View Quality + Code

i followed a quick tutorial on how to create a custom camera view on youtube and its working fine...but for some reason the camera is zoomed in ( not like the regular camera of the iPhone & the quality seems like downgraded,Plus i don't know if the code is very optimized so i thought i should ask some professionals online like you guys :) How can i improve\Maximize my camera Quality & performance?
here is my code:
Custom Camera View Code:
import UIKit
import AVFoundation
class CustomCameraViewController: UIViewController,AVCaptureVideoDataOutputSampleBufferDelegate,UIImagePickerControllerDelegate{
#IBOutlet var CameraView: UIView!
var audioPlayer = AVAudioPlayer()
let captureSession = AVCaptureSession()
var captureDevice: AVCaptureDevice?
var previewLayer : AVCaptureVideoPreviewLayer?
var frontCamera: Bool = false
var stilledImageOutput: AVCaptureStillImageOutput = AVCaptureStillImageOutput()
func beginSession(){
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
self.view.layer.addSublayer(previewLayer!)
previewLayer?.frame = self.view.layer.bounds
previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
captureSession.startRunning()
stilledImageOutput.outputSettings = [AVVideoCodecKey : AVVideoCodecJPEG ]
if captureSession.canAddOutput(stilledImageOutput){
captureSession.addOutput(stilledImageOutput)
}
}
func frontCamera(_ front: Bool){
let devices = AVCaptureDevice.devices()
do{
try captureSession.removeInput(AVCaptureDeviceInput(device:captureDevice))
}catch{
print("Error")
}
for device in devices!{
if((device as AnyObject).hasMediaType(AVMediaTypeVideo)){
if front{
if (device as AnyObject).position == AVCaptureDevicePosition.front {
captureDevice = device as? AVCaptureDevice
do{
try captureSession.addInput(AVCaptureDeviceInput(device: captureDevice))
}catch{
}
break
}
}else{
if (device as AnyObject).position == AVCaptureDevicePosition.back {
captureDevice = device as? AVCaptureDevice
do{
try captureSession.addInput(AVCaptureDeviceInput(device: captureDevice))
}catch{
}
break
}
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 10.0, *) {
let photoSettings = AVCapturePhotoSettings()
photoSettings.isHighResolutionPhotoEnabled = true
photoSettings.isAutoStillImageStabilizationEnabled = true
} else {
// Fallback on earlier versions.
}
frontCamera(frontCamera)
if captureDevice != nil{
beginSession()
let music = Bundle.main.path(forResource: "CameraShutterSFX", ofType: "mp3")
// copy this syntax, it tells the compiler what to do when action is received
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: music! ))
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
try AVAudioSession.sharedInstance().setActive(true)
}
catch{
print("error playing sound")
}
}
}
func TakePhoto(_ sender: UIButton) {
audioPlayer.play()
audioPlayer.volume = 0.1
if #available(iOS 10.0, *) {
let photoSettings = AVCapturePhotoSettings()
photoSettings.isHighResolutionPhotoEnabled = true
photoSettings.isAutoStillImageStabilizationEnabled = true
} else {
// Fallback on earlier versions.
}
}
func ActivateFlash(_ sender: UIButton) {
let FlashValue = !sender.isSelected
sender.isSelected = FlashValue
if captureDevice!.hasTorch{
do{
try captureDevice!.lockForConfiguration()
captureDevice!.torchMode = captureDevice!.isTorchActive ? AVCaptureTorchMode.off : AVCaptureTorchMode.on
captureDevice!.unlockForConfiguration()
}catch{
}
}
}
func DismissAction(_ sender: UIButton) {
performSegue(withIdentifier: "Segue", sender: nil)
}
func SwitchCameraDirectionsButton(_ sender: Any) {
//Switch Camera to Front:
frontCamera = !frontCamera
captureSession.beginConfiguration()
let inputs = captureSession.inputs as! [AVCaptureInput]
for oldInput: AVCaptureInput in inputs{
captureSession.removeInput(oldInput)
}
frontCamera(frontCamera)
captureSession.commitConfiguration()
}
}
it doesn't take Photos yet...but i would really love to maximize my Camera Performance and quality before i continue forward,i hope you understand , Thank you for helping :)

Resources