AVFoundation切换宽/超广角时Torch开启致相机冻结问题求助
解决AVFoundation广角/超广角切换时Torch导致的相机冻结问题
问题本质
超广角相机(.builtInUltraWideCamera)本身没有物理Torch硬件,原生相机是通过共享广角相机的Torch来实现补光的。直接给超广角设备设置torchMode,或者切换时Torch状态管理不当,会导致AVFoundation内部硬件冲突,引发相机冻结。
针对疑问的解答
这是AVFoundation的已知限制吗?有没有Workaround?
这不是AVFoundation的限制,而是对硬件逻辑的误解——超广角无独立Torch。Workaround是:无论当前使用广角还是超广角,Torch的控制都统一操作广角相机的硬件,切换相机时先暂存Torch状态,切换完成后再恢复。Apple原生相机如何实现超广角搭配Torch?
原生相机内部并未给超广角设备设置Torch,而是保持广角相机的Torch开启,利用硬件共享机制为超广角采集补光。系统层面处理了硬件资源的协调,避免了冲突。Torch激活状态下切换相机,如何避免冻结?
核心操作流程:
- 切换前先记录当前Torch开启状态
- 关闭当前激活相机的Torch(如果开启)
- 执行相机输入切换(在session的begin/commit配置块内完成)
- 切换完成后,若之前Torch是开启状态,开启广角相机的Torch(无论当前用的是广角还是超广角)
- 所有设备配置操作必须在同一个串行队列执行,禁止跨队列并发修改
- 如何正确同步Torch激活与相机切换?
严格遵循「先关Torch → 切换相机 → 再开Torch」的顺序,且所有设备配置(lockForConfiguration、torchMode修改)必须在AVCaptureSession的串行队列(或指定的串行队列)中执行,避免并发操作导致的硬件冲突。
修正后的代码实现
@IBAction func ultrawideCameraTapped(_ sender: Any?) { // 所有相机操作必须在session的串行队列执行,避免并发冲突 videoCapture.captureSession.queue.async { [weak self] in guard let self = self else { return } let isSwitchingToUltraWide = !self.isUsingFisheyeCamera let cameraType: AVCaptureDevice.DeviceType = isSwitchingToUltraWide ? .builtInUltraWideCamera : .builtInWideAngleCamera let cameraName = isSwitchingToUltraWide ? "Ultra Wide" : "Wide" guard let selectedCamera = AVCaptureDevice.default(cameraType, for: .video, position: .back) else { DispatchQueue.main.async { self.showAlert(title: "Camera Error", message: "\(cameraName) camera is not available on this device.") } return } // 1. 暂存当前Torch状态 let shouldRestoreTorch = self.isFlashlightOn // 2. 关闭当前激活相机的Torch if shouldRestoreTorch { self.disableTorch() } do { let currentInput = self.videoCapture.captureSession.inputs.first as? AVCaptureDeviceInput self.videoCapture.captureSession.beginConfiguration() if let currentInput = currentInput { self.videoCapture.captureSession.removeInput(currentInput) } let videoInput = try AVCaptureDeviceInput(device: selectedCamera) self.videoCapture.captureSession.addInput(videoInput) self.videoCapture.captureSession.commitConfiguration() self.videoCapture.updateVideoOrientation() // 3. 切换完成后恢复Torch状态(统一操作广角相机) if shouldRestoreTorch { self.enableTorch() } DispatchQueue.main.async { if let barButton = sender as? UIBarButtonItem { barButton.title = isSwitchingToUltraWide ? "Wide" : "Ultra Wide" barButton.tintColor = isSwitchingToUltraWide ? UIColor.systemGreen : UIColor.white } print("Switched to \(cameraName) camera.") } self.isUsingFisheyeCamera.toggle() } catch { DispatchQueue.main.async { self.showAlert(title: "Camera Error", message: "Failed to switch to \(cameraName) camera: \(error.localizedDescription)") } // 切换失败时恢复Torch状态 if shouldRestoreTorch { self.enableTorch() } } } } // 统一开启Torch:操作广角相机 private func enableTorch() { guard let wideCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back), wideCamera.hasTorch else { DispatchQueue.main.async { self.showAlert(title: "Torch Error", message: "Torch is not available on this device.") } return } do { try wideCamera.lockForConfiguration() wideCamera.torchMode = .on self.isFlashlightOn = true wideCamera.unlockForConfiguration() } catch { DispatchQueue.main.async { self.showAlert(title: "Torch Error", message: "Failed to enable torch: \(error.localizedDescription)") } self.isFlashlightOn = false } } // 统一关闭Torch:操作当前激活的相机(或直接操作广角) private func disableTorch() { // 优先关闭当前激活相机的Torch,避免资源占用 if let currentInput = videoCapture.captureSession.inputs.first as? AVCaptureDeviceInput { let currentCamera = currentInput.device if currentCamera.hasTorch { do { try currentCamera.lockForConfiguration() currentCamera.torchMode = .off currentCamera.unlockForConfiguration() } catch { // 容错:直接操作广角相机关闭 self.forceDisableWideCameraTorch() } } else { // 当前是超广角,直接关闭广角的Torch self.forceDisableWideCameraTorch() } } else { self.forceDisableWideCameraTorch() } self.isFlashlightOn = false } private func forceDisableWideCameraTorch() { guard let wideCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back), wideCamera.hasTorch else { return } do { try wideCamera.lockForConfiguration() wideCamera.torchMode = .off wideCamera.unlockForConfiguration() } catch {} }
关键优化点
- 所有相机/Torch操作统一在
captureSession的串行队列执行,避免并发冲突 - 切换相机前先关闭Torch,切换完成后再恢复,彻底避免硬件资源冲突
- Torch的开启/关闭统一操作广角相机,适配超广角无Torch的硬件逻辑
- 增加了切换失败时的Torch状态恢复,保证状态一致性
内容的提问来源于stack exchange,提问作者Danilo Birbiglia
相关产品推荐
相关产品推荐

