I have created code for iOS that allows me to start and stop video acquisition from a proprietary USB camera using AVFoundation's AVCaptureSession and AVCaptureDevice APIs.
There is a start and stop method. The start method takes an argument to specify one of two formats that I use for my custom camera application. I can start the session and switch between formats all day without any errors. However, if I start and then stop the camera three times in a row, on the third invocation of start, I get errors in the console output and the CMSampleBuffers stop flowing to my callback.
Additionally, once I get AVFoundation into this state, stoping the camera doesn't help. I have to kill the app and start over.
Here are the errors. And below these, the code. I'm hoping someone who has experience with these errors or an engineer from Apple who knows the AVFoundation image capture pipeline code, can respond and tell me what I'm doing wrong. Thanks.
<<<< FigCaptureSourceRemote >>>> Fig assert: "! storage->connectionDied" at bail (FigCaptureSourceRemote.m:235) - (err=0) <<<< FigCaptureSourceRemote >>>> Fig assert: "err == 0 " at bail (FigCaptureSourceRemote.m:558) - (err=-16453) <<<< FigCaptureSourceRemote >>>> Fig assert: "! storage->connectionDied" at bail (FigCaptureSourceRemote.m:235) - (err=0) <<<< FigCaptureSourceRemote >>>> Fig assert: "err == 0 " at bail (FigCaptureSourceRemote.m:253) - (err=-16453) <<<< FigCaptureSourceRemote >>>> Fig assert: "err == 0 " at bail (FigCaptureSourceRemote.m:269) - (err=-16453) <<<< FigCaptureSourceRemote >>>> Fig assert: "err == 0 " at bail (FigCaptureSourceRemote.m:511) - (err=-16453) Capture session error: The operation could not be completed Capture session error: The operation could not be completed
func start(for deviceFormat: String) async throws -> AnyPublisher<CMSampleBuffer, Swift.Error> {
func configureCaptureDevice(with deviceFormat: String) throws {
guard let format = formatDict[deviceFormat] else { throw Error.captureFormatNotFound }
captureSession.beginConfiguration()
defer { captureSession.commitConfiguration() }
try captureDevice.lockForConfiguration()
captureDeviceFormat = deviceFormat
captureDevice.activeFormat = format
captureDevice.unlockForConfiguration()
}
return try await withCheckedThrowingContinuation { continuation in
sessionQueue.async { [unowned self] in
logger.debug("Start capture session for \(deviceFormat): \(String(describing: captureSession))")
// If we were already steaming camera images from a different mode, terminate that stream.
bufferPublisher?.send(completion: .finished)
bufferPublisher = nil
captureDeviceFormat = ""
do {
// Re-configure with the new format; should be harmless if called with the currently configured format.
try configureCaptureDevice(with: deviceFormat)
// Return a new stream publisher for this invocation.
bufferPublisher = PassthroughSubject<CMSampleBuffer, Swift.Error>()
// If we are not currently running, start the image capture pipeline.
if captureSession.isRunning == false {
captureSession.startRunning()
}
continuation.resume(returning: bufferPublisher!.eraseToAnyPublisher())
} catch {
logger.fault("Failed to start camera: \(error.localizedDescription)")
continuation.resume(throwing: error)
}
}
}
}
func stop() async throws {
try await withCheckedThrowingContinuation { continuation in
sessionQueue.async { [unowned self] in
logger.debug("Stop capture session: \(String(describing: captureSession))")
// The following invocation is synchronous and takes time to execute;
// looks like a stall but you can ignore it as the MainActor is not blocked.
captureSession.stopRunning()
// Terminate the stream and reset our state.
bufferPublisher?.send(completion: .finished)
bufferPublisher = nil
captureDeviceFormat = ""
// Signal the caller that we are done here.
continuation.resume()
}
}
}