我不确定我的帖子是否完全符合规则,或者是否有人能够克隆该 repo,但我被困住了。
将 Swift 语言版本从 5 更改为 6 并对此处提供的 Apple 示例项目进行一些调整(严格并发):https ://developer.apple.com/tutorials/sample-apps/capturingphotos-captureandsave ,不幸的是它在打开时崩溃了。
我试图理解为什么会发生这种情况。我注意到,虽然照片库在应用程序中运行良好,但相机会话却不行。我的问题是:有人知道我可能做错了什么吗?有趣的是,如果我恢复到 Swift 5,即使进行了更改也不会发生崩溃。
这里是一些相关的代码片段,我在其中配置捕获会话、启动捕获会话以及处理捕获缓冲区:
class Camera {
…
private func configureCaptureSession(completionHandler: (_ success: Bool) -> Void) {
var success = false
self.captureSession.beginConfiguration()
defer {
self.captureSession.commitConfiguration()
completionHandler(success)
}
guard
let captureDevice = captureDevice,
let deviceInput = try? AVCaptureDeviceInput(device: captureDevice)
else {
logger.error("Failed to obtain video input.")
return
}
let photoOutput = AVCapturePhotoOutput()
captureSession.sessionPreset = AVCaptureSession.Preset.photo
let videoOutput = AVCaptureVideoDataOutput()
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "VideoDataOutputQueue"))
guard captureSession.canAddInput(deviceInput) else {
logger.error("Unable to add device input to capture session.")
return
}
guard captureSession.canAddOutput(photoOutput) else {
logger.error("Unable to add photo output to capture session.")
return
}
guard captureSession.canAddOutput(videoOutput) else {
logger.error("Unable to add video output to capture session.")
return
}
captureSession.addInput(deviceInput)
captureSession.addOutput(photoOutput)
captureSession.addOutput(videoOutput)
self.deviceInput = deviceInput
self.photoOutput = photoOutput
self.videoOutput = videoOutput
photoOutput.isHighResolutionCaptureEnabled = true
photoOutput.maxPhotoQualityPrioritization = .quality
updateVideoOutputConnection()
isCaptureSessionConfigured = true
success = true
}
…
func start() async {
let authorized = await checkAuthorization()
guard authorized else {
logger.error("Camera access was not authorized.")
return
}
if isCaptureSessionConfigured {
if !captureSession.isRunning {
sessionQueue.async { [self] in
self.captureSession.startRunning()
}
}
return
}
sessionQueue.async { [self] in
self.configureCaptureSession { success in
guard success else { return }
self.captureSession.startRunning()
}
}
}
}
extension Camera: @preconcurrency AVCaptureVideoDataOutputSampleBufferDelegate {
@MainActor
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let pixelBuffer = sampleBuffer.imageBuffer else { return }
guard let device = captureDevice else { return }
let rotationCoordiantor = AVCaptureDevice.RotationCoordinator(
device: device,
previewLayer: nil
)
if connection.isVideoOrientationSupported {
connection.videoRotationAngle = rotationCoordiantor.videoRotationAngleForHorizonLevelCapture
}
addToPreviewStream?(CIImage(cvPixelBuffer: pixelBuffer))
}
}
如果有人愿意看一看,我准备了一个包含两个提交的仓库。在初始提交中,我以 1 对 1 的方式添加了 Apple 的示例代码,在“Swift 6 更新”提交中,我做了一些调整以消除编译器错误。但是,我不确定我是否做对了。
仓库链接:https://github.com/miltenkot/CapturingPhotosSwift6
附言:要检查相机会话,您需要在真实设备上打开此示例,因为在模拟器上您只能打开库并且不会崩溃
总结
问题在于,
AVCaptureVideoDataOutputSampleBufferDelegate
协议具有非隔离要求captureOutput
,但您的实现与主要参与者隔离。隔离方法不能用于满足非隔离协议要求。当您收到此错误时,您可以展开左侧的面板,您将看到崩溃的线程的名称,即
VideoDataOutputQueue
:这是与实例化时提供的队列关联的线程
AVCaptureVideoDataOutput
,并且指定了其缓冲区委托和队列:AVCaptureVideoDataOutputSampleBufferDelegate
该队列由方法使用captureOutput
。但是您已将该方法标记为
@MainActor
(大概是为了访问deviceOrientation
与主要参与者隔离的属性):使用
@preconcurrency
(诚然,编译器实际上建议作为一种可能的解决方法)可以消除严重的编译器错误:底线是,不能只将参与者隔离添加到由某些框架调用的委托方法中。框架决定如何调用它,而不是你。你必须删除
@MainActor
上的限定符captureOutput
。一旦你这样做,崩溃就会消失。