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

Swift使用AVCaptureSession录制视频二次点击崩溃问题求助

解决AVCaptureSession第二次录制崩溃的问题

问题根源分析

从你给出的报错信息An AVCaptureOutput instance may not be added to more than one session和代码来看,核心问题在于每次页面出现(viewWillAppear)都会执行session.addOutput(movieOutput),但movieOutput是类的成员变量,当它已经被添加到session中时,重复调用addOutput就会触发这个异常。

具体拆解你的代码流程:

  1. 第一次进入页面:viewWillAppear配置session并添加movieOutput,录制正常;点击停止后你调用了session.removeOutput(movieOutput)。
  2. 第二次进入页面(比如返回后再次进入):viewWillAppear再次执行session.addOutput(movieOutput)——如果此时movieOutput还在session中(比如你没点击停止就离开页面),就会直接抛出异常。另外,session作为成员变量被重复配置输入输出,也会带来额外的状态混乱。

修复方案

我帮你调整代码结构,让session和output的管理更严谨:

1. 把session配置移到viewDidLoad,避免重复初始化

将session的输入输出配置、预览层设置逻辑移到viewDidLoad,只执行一次:

class CaptureViewViewController: BaseViewController,AVCaptureFileOutputRecordingDelegate{
    let session: AVCaptureSession = AVCaptureSession()
    var movieOutput = AVCaptureMovieFileOutput()
    var previewLayer: AVCaptureVideoPreviewLayer? // 保存预览层引用,避免重复添加
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupCaptureSession()
    }
    
    private func setupCaptureSession() {
        session.beginConfiguration()
        session.sessionPreset = AVCaptureSession.Preset.high
        
        // 添加音频输入
        if let audioDevice = AVCaptureDevice.default(for: AVMediaType.audio){
            do {
                let audioInput = try AVCaptureDeviceInput(device: audioDevice)
                if session.canAddInput(audioInput) {
                    session.addInput(audioInput)
                }
            } catch {
                print("Unable to add audio device to the recording.")
            }
        }
        
        // 添加视频输入
        if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .front) {
            do {
                let videoInput = try AVCaptureDeviceInput(device: device)
                if session.canAddInput(videoInput) {
                    session.addInput(videoInput)
                }
            } catch {
                print(error.localizedDescription)
            }
            
            // 配置预览层
            previewLayer = AVCaptureVideoPreviewLayer(session: session)
            self.preViewImage.layer.addSublayer(previewLayer!)
            previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
            previewLayer?.frame = self.preViewImage.layer.bounds
        }
        
        // 检查并添加视频输出
        if session.canAddOutput(movieOutput) {
            movieOutput.movieFragmentInterval = CMTime.invalid
            session.addOutput(movieOutput)
        }
        
        session.commitConfiguration()
    }

2. 简化页面生命周期逻辑

只在页面出现时启动session,消失时停止session,避免重复操作:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    // 避免重复启动session
    if !session.isRunning {
        session.startRunning()
    }
    // 更新预览层frame,适配页面布局变化
    previewLayer?.frame = self.preViewImage.layer.bounds
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    // 页面消失时强制停止录制,重置状态
    if videoRecordState == .start {
        movieOutput.stopRecording()
        recordVideoButton.setTitle("Start Recording", for: .normal)
        videoRecordState = .stop
    }
    // 停止session
    if session.isRunning {
        session.stopRunning()
    }
}

3. 优化录制按钮逻辑

移除不必要的session.removeOutput(movieOutput),复用同一个output实例:

@IBAction func handleMoviewRecord(_ sender: Any) {
    if videoRecordState == .stop {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        let fileUrl = paths[0].appendingPathComponent("output.mov")
        try? FileManager.default.removeItem(at: fileUrl)
        
        // 保险检查:确保output在session中
        if !session.outputs.contains(movieOutput) {
            if session.canAddOutput(movieOutput) {
                session.addOutput(movieOutput)
            }
        }
        
        movieOutput.startRecording(to: fileUrl, recordingDelegate: self)
        recordVideoButton.setTitle("Stop Recording", for: .normal)
        recordVideoButton.backgroundColor = UIColor.red
        videoRecordState = .start
    }else {
        movieOutput.stopRecording()
        recordVideoButton.setTitle("Start Recording", for: .normal)
        videoRecordState = .stop
        // 无需移除output,后续录制还要复用
    }
}

额外注意点

  • 每次添加输入输出前,务必用canAddInput/canAddOutput做检查,避免触发异常。
  • 保存previewLayer的引用,在页面布局变化时更新frame,保证预览画面正常显示。
  • 页面消失时强制重置录制状态,避免回到页面后出现状态混乱。

内容的提问来源于stack exchange,提问作者Manjula Nang

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 11:04:13