How do I record a video on iOS without using a preset?
Asked Answered
J

1

4

The simpler way to record a video on iOS is by setting a AVCaptureSession.sessionPreset.

But that doesn't work for me since I want to control parameters like binning, stabilization (cinematic, standard, or none) and ISO.

I find the format I want and assign it to activeFormat, but when I try to start recording, I get an error:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 
'*** -[AVCaptureMovieFileOutput startRecordingToOutputFileURL:recordingDelegate:] No active/enabled connections'

Here is my initialisation code:

let device = AVCaptureDevice.defaultDevice(
    withDeviceType: .builtInWideAngleCamera,
    mediaType: AVMediaTypeVideo,
    position: .back)!
let session = AVCaptureSession()
session.addInput(try! AVCaptureDeviceInput(device: device))
output = AVCaptureMovieFileOutput()
session.addOutput(output)
device.setFormatWithHighestIso()
session.startRunning()

setFormatWithHighestIso() is defined as:

extension AVCaptureDevice {
  var goodVideoFormats: [AVCaptureDeviceFormat] {
    return (formats as! [AVCaptureDeviceFormat])
      .filter { CMFormatDescriptionGetMediaSubType($0.formatDescription) != 875704422 } // 420f
      .filter { $0.autoFocusSystem == .phaseDetection }
  }

  func setFormatWithHighestIso() {
    let format = goodVideoFormats
      .filter { $0.maxISO > 1759 }
      .filter { $0.height < 1937 }
      .first!

    try! lockForConfiguration()
    defer { unlockForConfiguration() }
    activeFormat = format
    NSLog("\(format)")
  }
}

The last log statement produces:

<AVCaptureDeviceFormat: 0x1702027d0 'vide'/'420f' 2592x1936, { 3- 30 fps}, HRSI:4032x3024, fov:58.986, max zoom:189.00 (upscales @1.56), AF System:2, ISO:22.0-1760.0, SS:0.000005-0.333333, supports wide color>

This is indeed the format I want, so setFormatWithHighestIso() is working as expected. See the Apple reference.


Some other things I tried:

  • Using 420v instead of 420f, by changing the == 875704422 to !=.
  • Instead of starting the camera in photo mode, starting it in video mode, and then changing it to video mode by removing the AVCapturePhotoOutput and adding the AVCaptureMovieFileOutput.
  • Verifying that the AVCaptureConnection is enabled, and it is.
  • Verifying that the connection is active, but it's not:

    let conn = output.connection(withMediaType: AVMediaTypeVideo)! verify(conn.isActive)

I also tried using some other AVCaptureDeviceFormats, and they work:

extension AVCaptureDevice { 
  func setFormatWithCinematicVS() {
    let format = goodVideoFormats
      .filter { $0.isVideoStabilizationModeSupported(.cinematic) }
      .filter { $0.height == 720 }
      .first!

    try! lockForConfiguration()
    defer { unlockForConfiguration() }
    activeFormat = format
  }

  func setFormatWithStandardVS() {
    let format = goodVideoFormats
      .filter { $0.isVideoStabilizationModeSupported(.standard) }
      .filter { $0.height == 540 }
      .first!

    try! lockForConfiguration()
    defer { unlockForConfiguration() }
    activeFormat = format
  }
}

It's only the format with the highest ISO that doesn't work. What's special about this format?

Do I need to manually create an AVCaptureConnection? But there's already a connection; it's just not active.

This is on the iPhone 7 Plus running iOS 10.3.3. How do I record video in a specific format by setting the activeFormat without using a session?

If, instead of assigning to activeFormat, I use a sessionPreset, it does record a video successfully.


There are other questions talking of this error message, but this isn't a dupe of them since I specifically need to capture video without using a preset.

Jervis answered 11/9, 2017 at 16:33 Comment(4)
I'd like to try this out but don't have a 7+ - there is no analogous error on other models? Could you add the error message? And maybe a link to a runnable repro?Midshipman
I included the error in the question. I'll try to create a runnable repro, but in the mean time, what devices do you have? I'll tell you what format to try to repro.Jervis
I've got a 6s - will that work?Midshipman
For the 6s, in my code, please change maxISO > 1759 to 1839 and you should see the problem.Jervis
J
0

The solution turned out to be configuring the AVCaptureDevice before adding it to a session. Instead of:

session.addInput(try! AVCaptureDeviceInput(device: device))
output = AVCaptureMovieFileOutput()
session.addOutput(output)
device.setFormatWithHighestIso()

You should do:

device.setFormatWithHighestIso()  // Do this first!
session.addInput(try! AVCaptureDeviceInput(device: device))
output = AVCaptureMovieFileOutput()
session.addOutput(output)

When the device is added to the session, an AVCaptureConnection is created and configured in a certain way. If you change the device's resolution later, the configuration no longer matches, so the connection gets deactivated, and the video won't record.

Jervis answered 15/9, 2017 at 4:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.