On iOS 26, the first ProRes RAW recording after launching the app consistently stalls for the entire take: audio records normally, but video contains only a few frames (for example, an 11.8-second clip at approximately 0.25 fps instead of 30 fps). Every subsequent recording works correctly. Before startRecording, the Bayer format is active, ProRes RAW is available, white balance is locked, and frame duration is pinned to 1/30. Reordering setOutputSettings and reasserting configuration have not resolved it; only discarding the first recording acts as a reliable warm-up. Is this a known one-time ProRes RAW encoder initialisation issue, and is there a supported way to prepare the encoder before the first user takes?
I have a full working sample code if anyone needs but here is the setup function:
import CoreMedia
import CoreVideo
@available(iOS 26.0, *)
final class ProResRAWRecorder: NSObject,
AVCaptureFileOutputRecordingDelegate {
let session = AVCaptureSession()
private let movieOutput = AVCaptureMovieFileOutput()
private var camera: AVCaptureDevice!
// Call on a serial capture queue.
func configure() throws {
session.beginConfiguration()
defer { session.commitConfiguration() }
session.sessionPreset = .inputPriority
session.automaticallyConfiguresCaptureDeviceForWideColor = false
guard let device = AVCaptureDevice.default(
.builtInWideAngleCamera,
for: .video,
position: .back
) else {
throw SampleError.noCamera
}
let cameraInput = try AVCaptureDeviceInput(device: device)
guard session.canAddInput(cameraInput) else {
throw SampleError.cannotAddInput
}
session.addInput(cameraInput)
camera = device
// Optional, but reproduces the real topology where audio remains healthy.
if let microphone = AVCaptureDevice.default(for: .audio),
let microphoneInput = try? AVCaptureDeviceInput(device: microphone),
session.canAddInput(microphoneInput) {
session.addInput(microphoneInput)
}
// Find a 12-bit packed Bayer format supporting 30 fps.
guard let rawFormat = device.formats
.filter({
CMFormatDescriptionGetMediaSubType($0.formatDescription)
== kCVPixelFormatType_96VersatileBayerPacked12
&& $0.videoSupportedFrameRateRanges.contains {
$0.minFrameRate <= 30 && $0.maxFrameRate >= 30
}
})
.max(by: {
let a = CMVideoFormatDescriptionGetDimensions($0.formatDescription)
let b = CMVideoFormatDescriptionGetDimensions($1.formatDescription)
return Int(a.width) * Int(a.height)
< Int(b.width) * Int(b.height)
}) else {
throw SampleError.noRAWFormat
}
try device.lockForConfiguration()
device.activeFormat = rawFormat
if rawFormat.supportedColorSpaces.contains(.appleLog) {
device.activeColorSpace = .appleLog
}
let frameDuration = CMTime(value: 1, timescale: 30)
device.activeVideoMinFrameDuration = frameDuration
device.activeVideoMaxFrameDuration = frameDuration
if device.isWhiteBalanceModeSupported(.locked) {
device.whiteBalanceMode = .locked
}
device.unlockForConfiguration()
guard session.canAddOutput(movieOutput) else {
throw SampleError.cannotAddOutput
}
session.addOutput(movieOutput)
session.startRunning()
}
// Call on the same serial capture queue after startRunning() returns.
func record(to url: URL) throws {
guard let connection = movieOutput.connection(with: .video) else {
throw SampleError.noVideoConnection
}
// Reassert the required device state at the take boundary.
try camera.lockForConfiguration()
if camera.isWhiteBalanceModeSupported(.locked) {
camera.whiteBalanceMode = .locked
}
let frameDuration = CMTime(value: 1, timescale: 30)
camera.activeVideoMinFrameDuration = frameDuration
camera.activeVideoMaxFrameDuration = frameDuration
camera.unlockForConfiguration()
guard movieOutput.availableVideoCodecTypes.contains(.proResRAW) else {
throw SampleError.rawCodecUnavailable
}
movieOutput.setOutputSettings(
[AVVideoCodecKey: AVVideoCodecType.proResRAW],
for: connection
)
movieOutput.startRecording(to: url, recordingDelegate: self)
}
func stop() {
if movieOutput.isRecording {
movieOutput.stopRecording()
}
}
func fileOutput(
_ output: AVCaptureFileOutput,
didFinishRecordingTo outputFileURL: URL,
from connections: [AVCaptureConnection],
error: Error?
) {
print("Finished:", outputFileURL, "error:", error as Any)
}
enum SampleError: Error {
case noCamera
case cannotAddInput
case noRAWFormat
case cannotAddOutput
case noVideoConnection
case rawCodecUnavailable
}
}
Thanks for the thorough write-up and the sample code. What you describe matches a documented iOS 26 behavior: the first ProRes RAW take stalls, later takes are fine, and a discarded first recording reliably warms it up. There is a supported way to prepare the output that should remove the throwaway.
iOS 26 added isDeferredStartEnabled on AVCaptureOutput. For apps linked on or after iOS 26, it defaults to true for AVCaptureFileOutput subclasses, which includes AVCaptureMovieFileOutput. When it is true, the session does not prepare that output's resources until some time after startRunning() returns. So an early first startRecording can begin before the ProRes RAW encoder is prepared, which matches your slow first take. A discarded recording works because it gives that preparation time to finish.
The supported way to prepare the output before the first take is to turn deferred start off for the movie output. Then the session prepares it at startRunning():
session.addOutput(movieOutput)
if movieOutput.isDeferredStartSupported {
movieOutput.isDeferredStartEnabled = false
}
Set this before you commit the configuration, since it requires a pipeline reconfiguration. With deferred start off for the movie output, the encoder is prepared as part of starting the session. The first user take should then behave like the later ones, without a warm-up recording.
One related note on your configure(): the deferred commitConfiguration() runs after startRunning(), so the session starts before the configuration is committed. Committing the configuration before calling startRunning() is cleaner, and it makes sure the isDeferredStartEnabled change is committed before the session starts.
If you set isDeferredStartEnabled = false and still get a slow first take, that would be worth a Feedback Assistant report with your sample project. Since this maps to the documented deferred-start behavior, the property is the first thing to try.
For reference: