You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AVFoundation切换宽/超广角时Torch开启致相机冻结问题求助

解决AVFoundation广角/超广角切换时Torch导致的相机冻结问题

问题本质

超广角相机(.builtInUltraWideCamera)本身没有物理Torch硬件,原生相机是通过共享广角相机的Torch来实现补光的。直接给超广角设备设置torchMode,或者切换时Torch状态管理不当,会导致AVFoundation内部硬件冲突,引发相机冻结。


针对疑问的解答

  1. 这是AVFoundation的已知限制吗?有没有Workaround?
    这不是AVFoundation的限制,而是对硬件逻辑的误解——超广角无独立Torch。Workaround是:无论当前使用广角还是超广角,Torch的控制都统一操作广角相机的硬件,切换相机时先暂存Torch状态,切换完成后再恢复。

  2. Apple原生相机如何实现超广角搭配Torch?
    原生相机内部并未给超广角设备设置Torch,而是保持广角相机的Torch开启,利用硬件共享机制为超广角采集补光。系统层面处理了硬件资源的协调,避免了冲突。

  3. Torch激活状态下切换相机,如何避免冻结?
    核心操作流程:

  • 切换前先记录当前Torch开启状态
  • 关闭当前激活相机的Torch(如果开启)
  • 执行相机输入切换(在session的begin/commit配置块内完成)
  • 切换完成后,若之前Torch是开启状态,开启广角相机的Torch(无论当前用的是广角还是超广角)
  • 所有设备配置操作必须在同一个串行队列执行,禁止跨队列并发修改
  1. 如何正确同步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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 17:17:03