Change FPS in AVFoundation's CaptureSession

33 views Asked by At

I'm trying to change the desired frame rate for my capture session. I believe this should be done with the following code:

for format in videoDevice.formats.reversed() {
                let ranges = format.videoSupportedFrameRateRanges
                let frameRates = ranges[0]

                if desiredFrameRate <= frameRates.maxFrameRate,
                   format.formatDescription.dimensions.width == sessionPreset.formatWidth,
                   format.formatDescription.dimensions.height == sessionPreset.formatHeight {
                    formatToSet = format
                    break
                }
            }

and

try videoDevice.lockForConfiguration()
                
                let dimensions = CMVideoFormatDescriptionGetDimensions((videoDevice.activeFormat.formatDescription))
                bufferSize.width = CGFloat(dimensions.width)
                bufferSize.height = CGFloat(dimensions.height)
                
                videoDevice.activeFormat = formatToSet

                let timescale = CMTimeScale(desiredFrameRate)
                if videoDevice.activeFormat.videoSupportedFrameRateRanges[0].maxFrameRate >= desiredFrameRate {
                    videoDevice.activeVideoMinFrameDuration = CMTime(value: 1, timescale: timescale)
                    videoDevice.activeVideoMaxFrameDuration = CMTime(value: 1, timescale: timescale)
                    
                    print(videoDevice.activeVideoMinFrameDuration)
                    print(videoDevice.activeVideoMaxFrameDuration)
                }
                
                videoDevice.unlockForConfiguration()

However in my case this brings no change whatsoever to the liquidity of camera motion. Am I doing something wrong?

Full file:

import AVFoundation

final class CaptureSessionManager: CaptureSessionManaging {
    private let captureSessionQueue = DispatchQueue(label: "captureSessionQueue")
    private let videoDataOutputQueue = DispatchQueue(label: "videoDataOutput",
                                                     qos: .userInitiated,
                                                     attributes: [],
                                                     autoreleaseFrequency: .workItem)
    
    private var bufferDelegate: AVCaptureVideoDataOutputSampleBufferDelegate?
    private var desiredFrameRate: Double?
    
    private var videoDevice: AVCaptureDevice?
    private let videoDevices: [AVCaptureDevice] = AVCaptureDevice.DiscoverySession(deviceTypes:
                                                                                    [.builtInWideAngleCamera, .builtInDualWideCamera, .builtInUltraWideCamera, .builtInTelephotoCamera],
                                                                                   mediaType: .video,
                                                                                   position: .back).devices
    var bufferSize: CGSize = .zero
    
    var captureSession: AVCaptureSession!
    
    func setUp(with bufferDelegate: AVCaptureVideoDataOutputSampleBufferDelegate,
               cameraPosition: AVCaptureDevice.Position,
               desiredFrameRate: Double,
               completion: @escaping () -> ()) {
        stopCaptureSession()
        
        self.bufferDelegate = bufferDelegate
        self.desiredFrameRate = desiredFrameRate
        
        chooseVideoDevice(cameraPosition: cameraPosition)
        
        authorizeCaptureSession {
            completion()
        }
    }
    
    private func chooseVideoDevice(cameraPosition: AVCaptureDevice.Position) {
        guard !videoDevices.isEmpty else {
            self.videoDevice = AVCaptureDevice.default(for: .video)
            return
        }
        
        switch cameraPosition {
        case .front:
            self.videoDevice = AVCaptureDevice.default(.builtInTrueDepthCamera, for: .video, position: .front)
        default:
            self.videoDevice = videoDevices.first(where: { device in device.position == cameraPosition })!
        }
    }
    
    private func authorizeCaptureSession(completion: @escaping () -> ())  {
        switch AVCaptureDevice.authorizationStatus(for: .video) {
        case .authorized:
            setupCaptureSession {
                completion()
            }
        case .notDetermined:
            AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
                if granted {
                    self?.setupCaptureSession {
                        completion()
                    }
                }
            }
        default:
            return
        }
    }
    
    private func setupCaptureSession(completion: @escaping () -> (), for captureDevice: AVCaptureDevice? = nil) {
        captureSessionQueue.async { [unowned self] in
            let captureSession: AVCaptureSession = AVCaptureSession()
            captureSession.beginConfiguration()
            
            guard let videoDevice = videoDevice else {
                return
            }
            
            do {
                let captureDeviceInput = try AVCaptureDeviceInput(device: videoDevice)
                guard captureSession.canAddInput(captureDeviceInput) else {
                    return
                }
                captureSession.addInput(captureDeviceInput)
            } catch {
                return
            }
            
            let videoOutput = AVCaptureVideoDataOutput()
            videoOutput.videoSettings = getVideoSettings()
            videoOutput.alwaysDiscardsLateVideoFrames = true
            videoOutput.setSampleBufferDelegate(self.bufferDelegate,
                                                queue: videoDataOutputQueue)
            
            let sessionPreset: SessionPreset = .hd1280x720
            
            var formatToSet: AVCaptureDevice.Format = videoDevice.formats[0]
            
            guard let desiredFrameRate = desiredFrameRate else {
                return
            }
            
            for format in videoDevice.formats.reversed() {
                let ranges = format.videoSupportedFrameRateRanges
                let frameRates = ranges[0]

                if desiredFrameRate <= frameRates.maxFrameRate,
                   format.formatDescription.dimensions.width == sessionPreset.formatWidth,
                   format.formatDescription.dimensions.height == sessionPreset.formatHeight {
                    formatToSet = format
                    break
                }
            }
            
            do {
                try videoDevice.lockForConfiguration()
                
                let dimensions = CMVideoFormatDescriptionGetDimensions((videoDevice.activeFormat.formatDescription))
                bufferSize.width = CGFloat(dimensions.width)
                bufferSize.height = CGFloat(dimensions.height)
                
                videoDevice.activeFormat = formatToSet

                let timescale = CMTimeScale(desiredFrameRate)
                if videoDevice.activeFormat.videoSupportedFrameRateRanges[0].maxFrameRate >= desiredFrameRate {
                    videoDevice.activeVideoMinFrameDuration = CMTime(value: 1, timescale: timescale)
                    videoDevice.activeVideoMaxFrameDuration = CMTime(value: 1, timescale: timescale)
                    
                    print(videoDevice.activeVideoMinFrameDuration)
                    print(videoDevice.activeVideoMaxFrameDuration)
                }
                
                videoDevice.unlockForConfiguration()
            } catch {
                return
            }
            
            guard captureSession.canAddOutput(videoOutput) else {
                return
            }
            
            let captureConnection = videoOutput.connection(with: .video)
            captureConnection?.isEnabled = true
            
            captureSession.addOutput(videoOutput)
            
            videoOutput.connection(with: .video)?.videoOrientation = .portrait
            
            captureSession.sessionPreset = sessionPreset.preset
            captureSession.commitConfiguration()
            
            self.captureSession = captureSession
            self.startCaptureSession()
            completion()
        }
    }
    
    private func getVideoSettings() -> [String: Any]? {
        [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)]
    }
    
    func startCaptureSession() {
        self.captureSession?.startRunning()
    }
    
    func stopCaptureSession() {
        self.captureSession?.stopRunning()
    }
}

protocol CaptureSessionManaging {
    var bufferSize: CGSize { get }
    var captureSession: AVCaptureSession! { get }
    
    func setUp(with bufferDelegate: AVCaptureVideoDataOutputSampleBufferDelegate,
               cameraPosition: AVCaptureDevice.Position,
               desiredFrameRate: Double,
               completion: @escaping () -> ())
    func startCaptureSession()
    func stopCaptureSession()
}

I'm setuping the session in View Controllers' viewDidLoad methods like this:

override func viewDidLoad() {
        super.viewDidLoad()
        captureSessionManager.setUp(with: self,
                                    cameraPosition: .back,
                                    desiredFrameRate: 16.34) {
            self.setupSessionPreviewLayer()
        }
    }

private func setupSessionPreviewLayer() {
        screenRect = UIScreen.main.bounds
        previewLayer = AVCaptureVideoPreviewLayer(session: captureSessionManager.captureSession)
        previewLayer.frame = CGRect(x: 0,
                                    y: 0,
                                    width: screenRect.size.width,
                                    height: screenRect.size.height)
        previewLayer.videoGravity = .resizeAspectFill
        previewLayer.connection?.videoOrientation = .portrait
        
        DispatchQueue.main.async { [weak self] in
            guard let self = self else {
                return
            }
            
            self.view.layer.addSublayer(self.previewLayer)
        }
    }
0

There are 0 answers