macOS应用中AVCapturePhotoOutput无法捕获保存图片的问题
macOS应用摄像头捕获图片失败问题排查
问题描述
开发macOS应用,点击按钮调用前置摄像头捕获图片,摄像头绿灯亮起但图片未保存,控制台输出以下错误日志:
2023-08-13 13:02:54.978411+0200 button_to_image[23599:3209684] Metal API Validation Enabled 2023-08-13 13:02:55.155150+0200 button_to_image[23599:3209684] [] CMIO_DAL_PlugInManagement.cpp:880:CreatePlugIn Could not find plugin with kCMIOHardwarePlugInTypeID 2023-08-13 13:02:55.175844+0200 button_to_image[23599:3209684] [plugin] AddInstanceForFactory: No factory registered for id <CFUUID 0x6000039a00a0> F8BB1C28-BAE8-11D6-9C31-00039315CD46 objc[23599]: class `NSKVONotifying_AVCapturePhotoOutput' not linked into application
已完成的配置:
- App Sandbox和Hardened Runtime中勾选摄像头权限
- 添加AVFoundation.framework框架
- Info.plist中添加
NSCameraUsageDescription字段 - 尝试过模拟器运行、导出本地运行,均未解决
相关代码:
import SwiftUI import AVFoundation import AppKit import Foundation struct ContentView: View { @State private var capturedImage: NSImage? = nil var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) Text("Click the button!") Button(action: { captureImage() }) { Text("Capture Image") } if let img = capturedImage { Image(nsImage: img) .resizable() .aspectRatio(contentMode: .fit) } } .padding() } func captureImage() { let captureSession = AVCaptureSession() guard let captureDevice = AVCaptureDevice.default(for: .video) else { return } guard let input = try? AVCaptureDeviceInput(device: captureDevice) else { return } captureSession.addInput(input) captureSession.startRunning() let output = AVCapturePhotoOutput() captureSession.addOutput(output) let settings = AVCapturePhotoSettings() output.capturePhoto(with: settings, delegate: PhotoCaptureDelegate { image in self.capturedImage = image }) } } class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate { let completionHandler: (NSImage?) -> Void init(completionHandler: @escaping (NSImage?) -> Void) { self.completionHandler = completionHandler } func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { guard let imageData = photo.fileDataRepresentation() else { return } let image = NSImage(data: imageData) completionHandler(image) // Save the image to a file let desktopURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first! let fileURL = desktopURL.appendingPathComponent("capturedImage.jpg") try? imageData.write(to: fileURL) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
问题分析与解决方案
核心问题1:AVCaptureSession生命周期过短
captureImage()方法中AVCaptureSession是局部变量,方法执行完毕后会被立即释放,导致捕获流程中断,无法触发didFinishProcessingPhoto回调。
核心问题2:AVCapturePhotoOutput添加时机错误
必须在captureSession.startRunning()之前添加输出,否则会话运行后添加输出可能不生效。
核心问题3:try?掩盖错误
保存图片时使用try?会忽略写入错误,无法排查权限或路径问题。
修复后的代码
import SwiftUI import AVFoundation import AppKit import Foundation struct ContentView: View { @State private var capturedImage: NSImage? = nil // 将captureSession提升为实例变量,延长生命周期 private var captureSession: AVCaptureSession? var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) Text("Click the button!") Button(action: { captureImage() }) { Text("Capture Image") } if let img = capturedImage { Image(nsImage: img) .resizable() .aspectRatio(contentMode: .fit) } } .padding() } func captureImage() { let session = AVCaptureSession() captureSession = session // 保存引用 // 配置会话为照片预设 session.sessionPreset = .photo // 明确指定前置摄像头 guard let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) else { print("无法获取前置摄像头") return } do { let input = try AVCaptureDeviceInput(device: captureDevice) if session.canAddInput(input) { session.addInput(input) } let output = AVCapturePhotoOutput() if session.canAddOutput(output) { session.addOutput(output) // 先添加输出再启动会话 } // 在后台队列启动会话,避免阻塞主线程 DispatchQueue.global(qos: .userInitiated).async { session.startRunning() // 延迟捕获,确保会话完全启动 DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { let settings = AVCapturePhotoSettings() output.capturePhoto(with: settings, delegate: PhotoCaptureDelegate { [weak self] image in guard let self = self else { return } self.capturedImage = image // 捕获完成后停止会话并释放引用 self.captureSession?.stopRunning() self.captureSession = nil }) } } } catch { print("配置摄像头失败: \(error.localizedDescription)") } } } class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate { let completionHandler: (NSImage?) -> Void init(completionHandler: @escaping (NSImage?) -> Void) { self.completionHandler = completionHandler } func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { if let error = error { print("捕获图片失败: \(error.localizedDescription)") completionHandler(nil) return } guard let imageData = photo.fileDataRepresentation() else { print("无法获取图片数据") completionHandler(nil) return } let image = NSImage(data: imageData) completionHandler(image) // 保存图片并处理错误 let desktopURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first! let fileURL = desktopURL.appendingPathComponent("capturedImage.jpg") do { try imageData.write(to: fileURL) print("图片已保存到: \(fileURL.path)") } catch { print("保存图片失败: \(error.localizedDescription)") } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
额外配置检查
- App Sandbox权限:除摄像头权限外,需勾选文件访问权限中的「User Selected File」,同时在
Info.plist中添加NSDesktopFolderUsageDescription字段,说明访问桌面的原因。 - Hardened Runtime:确保「Camera」权限已勾选,未禁用必要系统功能。
- 测试环境:macOS模拟器不支持摄像头硬件,必须在真实Mac设备上测试。
内容的提问来源于stack exchange,提问作者yemy
相关产品推荐
相关产品推荐

