I need to record and save video from an iPhone Xs at the phone's max frame rate (240 fps). The saved file always ends up at 30 fps. I've been through a dozen guides/docs/Stack Overflow posts but have yet to hit on the right solution. I've tested by opening the recorded file in VLC as well as by extracting and counting frames.
What am I doing wrong?
Environment: Xcode 10.1, build target iOS 12.1, tested on an iPhone Xs running iOS 12.1.2
Here I access the device and configure it for the best frame rate it supports:
override func viewDidLoad() {
super.viewDidLoad()
let deviceDiscoverSession = AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .back)
guard let backCameraDevice = deviceDiscoverSession else {
print("Failed to open back, wide-angle camera")
return
}
self.currentDevice = backCameraDevice
do {
let input = try AVCaptureDeviceInput(device: backCameraDevice)
// configureDevice() // putting this here makes no difference
self.session.addInput(input)
configureDevice()
} catch {
print(error)
return
}
}
func configureDevice() {
var bestFormat: AVCaptureDevice.Format? = nil
var bestFrameRateRange: AVFrameRateRange? = nil
var bestPixelArea: Int32 = 0
for format in currentDevice!.formats {
let dims: CMVideoDimensions = CMVideoFormatDescriptionGetDimensions(format.formatDescription)
let pixelArea: Int32 = dims.width * dims.height
let ranges = format.videoSupportedFrameRateRanges
for range in ranges {
if bestFrameRateRange==nil || range.maxFrameRate > bestFrameRateRange!.maxFrameRate || ((range.maxFrameRate == bestFrameRateRange!.maxFrameRate) && (pixelArea > bestPixelArea)) {
bestFormat = format as AVCaptureDevice.Format
bestFrameRateRange = range
bestPixelArea = pixelArea
}
}
}
do {
try currentDevice!.lockForConfiguration()
if let best_format = bestFormat {
currentDevice!.activeFormat = best_format
currentDevice!.activeVideoMinFrameDuration = bestFrameRateRange!.minFrameDuration
currentDevice!.activeVideoMaxFrameDuration = bestFrameRateRange!.maxFrameDuration
}
} catch {
print(error)
}
let movieFileOutput = AVCaptureMovieFileOutput()
if self.session.canAddOutput(movieFileOutput) {
self.session.beginConfiguration()
self.session.addOutput(movieFileOutput)
self.session.sessionPreset = .high
if let connection = movieFileOutput.connection(with: .video) {
if movieFileOutput.availableVideoCodecTypes.contains(.h264) {
movieFileOutput.setOutputSettings([AVVideoCodecKey:
AVVideoCodecType.h264], for: connection)
}
}
self.session.commitConfiguration()
self.movieFileOutput = movieFileOutput
}
currentDevice!.unlockForConfiguration()
}
When the user stops recording, I call a function that in part contains the following code to save the file to the temp directory (later moved to the app's documents directory)
sessionQueue.async {
if !self.isRecording {
self.isRecording = true
let movieFileOutputConnection = self.movieFileOutput!.connection(with: .video)
movieFileOutputConnection?.videoOrientation = .landscapeRight
let availableVideoCodecTypes = self.movieFileOutput!.availableVideoCodecTypes
if availableVideoCodecTypes.contains(.h264) {
self.movieFileOutput!.setOutputSettings([AVVideoCodecKey: AVVideoCodecType.h264], for: movieFileOutputConnection!)
}
let outputFileName = NSUUID().uuidString
let outputFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent((outputFileName as NSString).appendingPathExtension("mp4")!)
self.movieFileOutput?.startRecording(to: URL(fileURLWithPath: outputFilePath), recordingDelegate: self)
} else {
self.movieFileOutput?.stopRecording()
self.isRecording = false
}
}
The typical answer seems to be to configure the device after adding it to the session. Calling configure before or after adding to the session doesn't seem to make a difference.
I've tried configuring movieFileOutput
right before the self.movieFileOutput?.startRecording()
call as well as where I show it above. Both give the same results.
I figured this out with a deeper read of https://stackoverflow.com/a/41109637/292947
In
configureDevice()
I was settingself.session.sessionPreset = .high
when in fact I need to setself.session.sessionPreset = .inputPriority
which is the Swift 4 equivalent to the AVCaptureSessionPresetInputPriority value suggested in the above answer.