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)
}
}