SwiftUI/AVFoundation实现摄像头翻转功能出现黑屏问题
AVFoundation 前后摄像头切换黑屏修复
问题现象
- 基于 SwiftUI + AVFoundation 开发相机应用,初始相机配置可正常运行
- 触发前后摄像头翻转操作后,预览画面直接变为黑屏
- 按公开通用方案(获取当前摄像头位置、切换对应位置设备、提交配置前移除旧输入)实现后问题仍存在
原始问题代码
class CameraViewModel: NSObject,ObservableObject,AVCaptureFileOutputRecordingDelegate, AVCapturePhotoCaptureDelegate{ ... @Published var session = AVCaptureSession() @objc dynamic var videoDeviceInput: AVCaptureDeviceInput! private let sessionQueue = DispatchQueue(label: "session queue") func setUp(){ do{ self.session.beginConfiguration() let cameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) let videoInput = try AVCaptureDeviceInput(device: cameraDevice!) let audioDevice = AVCaptureDevice.default(for: .audio) let audioInput = try AVCaptureDeviceInput(device: audioDevice!) // MARK: Audio Input if self.session.canAddInput(videoInput) && self.session.canAddInput(audioInput){ self.session.addInput(videoInput) self.session.addInput(audioInput) self.videoDeviceInput = videoInput } if self.session.canAddOutput(self.output){ self.session.addOutput(self.output) } if self.session.canAddOutput(self.photoOutput){ self.session.addOutput(self.photoOutput) } self.session.commitConfiguration() } catch{ print(error.localizedDescription) } } func changeCamera() { sessionQueue.async { if self.videoDeviceInput != nil { let currentVideoDevice = self.videoDeviceInput.device let currentPosition = currentVideoDevice.position let preferredPosition: AVCaptureDevice.Position switch currentPosition { case .unspecified, .front: preferredPosition = .back case .back: preferredPosition = .front @unknown default: print("Unknown capture position. Defaulting to back, dual-camera.") preferredPosition = .back } print("current pos is \(currentPosition.rawValue) and preferred position is \(preferredPosition.rawValue)") do{ self.session.beginConfiguration() //remove device as needed self.session.removeInput(self.videoDeviceInput) let newCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: preferredPosition) let newVideoInput = try AVCaptureDeviceInput(device: newCameraDevice!) let newAudioDevice = AVCaptureDevice.default(for: .audio) let newAudioInput = try AVCaptureDeviceInput(device: newAudioDevice!) // MARK: Audio Input if self.session.canAddInput(newVideoInput) && self.session.canAddInput(newAudioInput){ self.session.addInput(newVideoInput) self.session.addInput(newAudioInput) self.videoDeviceInput = newVideoInput } self.session.commitConfiguration() }catch{ print(error.localizedDescription) } } } } }
问题根因
切换摄像头的逻辑中存在冗余操作:音频输入和摄像头前后位置无关,初始化会话时添加一次即可,切换摄像头时不需要重新创建、添加音频输入。重复添加音频输入会导致会话配置校验失败,新的视频输入无法正常挂载到会话中,最终触发预览黑屏。
修复方案
删除changeCamera()方法中所有和音频输入相关的创建、添加逻辑,切换摄像头时仅处理视频输入的替换即可,修复后的切换方法代码如下:
func changeCamera() { sessionQueue.async { guard self.videoDeviceInput != nil else { return } let currentVideoDevice = self.videoDeviceInput.device let currentPosition = currentVideoDevice.position let preferredPosition: AVCaptureDevice.Position switch currentPosition { case .unspecified, .front: preferredPosition = .back case .back: preferredPosition = .front @unknown default: print("Unknown capture position. Defaulting to back camera.") preferredPosition = .back } do { self.session.beginConfiguration() // 仅移除旧的视频输入,不改动已添加的音频输入 self.session.removeInput(self.videoDeviceInput) guard let newCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: preferredPosition) else { self.session.commitConfiguration() return } let newVideoInput = try AVCaptureDeviceInput(device: newCameraDevice) if self.session.canAddInput(newVideoInput) { self.session.addInput(newVideoInput) self.videoDeviceInput = newVideoInput } self.session.commitConfiguration() } catch { print(error.localizedDescription) self.session.commitConfiguration() } } }
注意事项
- 所有
AVCaptureSession的配置修改操作,必须放在专属串行队列sessionQueue中执行,避免多线程并发修改会话状态引发异常 - 尽量避免强制解包设备、输入对象,增加空判断兜底,防止异常触发配置中断
- 音频输入在会话初始化阶段添加一次即可,后续切换摄像头、切换分辨率等操作都不需要重复处理音频输入
内容的提问来源于stack exchange,提问作者krish
相关产品推荐
相关产品推荐

