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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 05:06:04