How do I record a video on iOS without using a pre

2019-02-26 03:57发布

问题:

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.

回答1:

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.